Skip to content

Feat/memory chat agent - #76

Merged
HuntyAmour merged 39 commits into
dev/v2.0.0from
feat/memory-chat-agent
Sep 18, 2026
Merged

HuntyAmour merged 39 commits into
dev/v2.0.0from
feat/memory-chat-agent

Conversation

@HuntyAmour

Copy link
Copy Markdown
Collaborator

Pull Request

Summary

Describe the change in 1–2 sentences.

Type of change

  • Feature
  • Bug fix
  • Docs update
  • Chore

Docs Impact (required for any code or docs changes)

  • Does this change require docs updates? If yes, list pages:
  • Have you updated or added pages under docs/?
  • Did you build the site and run the link checker? (python website/build.py + python website/check_links.py)
  • Did you avoid banned placeholders? (no "TBD/coming soon")

Testing

Describe how you tested this change. Include commands and results.

Checklist

  • Tests pass (pytest -v)
  • Linting passes (make lint / make format)
  • Documentation updated (if applicable)

HuntyAmour and others added 30 commits September 10, 2026 16:24
- satisfy OpenAI strict structured output
- fall back to plain JSON when the extraction schema is refused
…at tenant models, and tenant chat clients

- Implement tests for memory settings in `test_memory_settings.py` to validate default values and environment overrides.
- Create `test_retrieval_graph_seeds.py` to ensure graph observation and memory seeding functionality works as expected.
- Add tests in `test_runtime_chat_tenant_models.py` to verify tenant-specific chat client configurations and model validations.
- Introduce `test_tenant_chat_clients.py` to check the caching and disposal behavior of tenant chat clients, including handling of fingerprints and secrets.
…context request

- Added `graph_seed_node_keys` to `RetrievalRequest` for additional observation nodes.
- Introduced `MemoryContextRequest` dataclass for managing memory context parameters.
- Updated `RetrievalFacade` to utilize new graph seed nodes in retrieval processes.
- Integrated memory context service into `HarborRAG` for improved memory management.
- Enhanced tests to validate new memory usage tracking and model telemetry.
- Updated dependencies in `pyproject.toml` and `uv.lock` for langchain integration.
- Introduced `KnowledgeReferenceStore` and `ReferenceValue` for managing typed references.
- Added `retrieval_cost.py` for handling retrieval cost accounting.
- Created `retrieval_inputs.py` for shared input validation helpers.
- Developed `retrieval_schemas.py` for canonical input schema builders for retrieval tools.
- Implemented `source_list_tool.py` for permission-scoped source discovery.
- Added `vector_search.py` for tenant-scoped vector retrieval with explicit controls.
- Enhanced unit tests for agent tools and conversation coordination.
- Updated document retrieval tests to ensure metadata accuracy and permission checks.
- Updated documentation to include chat release evaluation details.
- Improved chat modes with RAG and agent modes for better handling of multi-document queries.
- Modified reasoning effort handling in chat parameters to align with deployment capabilities.
- Added tests for reasoning opt-out scenarios and validation of reasoning capabilities.
- Introduced new scripts for controlled quality probing of chat models and API endpoints.
- Enhanced agent execution to better manage tool calls and reasoning efforts.
- Implemented checks for tool schema compliance and agent instructions clarity.
- Introduced new citation assessment logic to ensure only valid citations are included in agent responses.
- Added tests for citation validation, ensuring that invalid markers are correctly identified and excluded.
- Improved evidence extraction from tool results, ensuring only available and valid sources are cited.
- Updated chat quality probes to reject responses lacking evidence or containing inconsistent citation counts.
- Refactored citation-related functions for better clarity and maintainability.
… exports

- Deleted compatibility exports in `references.py`, `__init__.py`, `base.py`, `catalog_factory.py`, `describe_graph.py`, `describe_graph_schema.py`, `evidence_output_schema.py`, `graph_catalog.py`, `graph_search.py`, `graph_search_support.py`, `output_schemas.py`, `reader_base.py`, `reader_catalog.py`, `reader_support.py`, `reader_tools.py`, `retrieval_inputs.py`, `source_list_tool.py`, and `vector_search.py`.
- Updated imports in `base.py`, `server.py`, and various test files to use runtime tools directly.
- Changed tool specifications in `McpServer` and related classes to use `ToolSpec` and `BaseTool` from the runtime.
- Adjusted tests to reflect the new structure and imports from the runtime.
- Enhanced error logging in `memory_tools.py` for better clarity on indexing failures.
- Added new tests for conversation memory coordination in `test_in_memory_conversation_coordination.py`.
The shared ToolBudget landed with gaps that made the two transports disagree
and, on the agent path, made three tools uncallable.

- Declare jsonschema in harborrag-runtime. tools/budgets.py imports it at
  module level, so a wheel install without the mcp-server or an optional extra
  failed at import before any command ran.
- Bind a tool call to a tenant only where the schema declares one. The engine
  injected tenant_id unconditionally, which failed additionalProperties on
  describe_graph and on both memory tools -- long-term recall was unreachable
  through the agent loop. Where the property is declared the write stays
  unconditional, so a model-supplied tenant is still overwritten.
- Run check_call and check_results on the memory tools too. Their maxLength and
  additionalProperties bounds were advertised to the model and enforced by
  nothing; reject_owner_fields stays as the guard beneath.
- Report the constraint, not the payload. A combinator failure rendered as
  repr(instance), so a 60 KB filters dict came back as a 60 KB error that the
  engine then truncated the explanation off the end of.
- Cache compiled validators by canonical schema text. check_schema ran per call
  and dominated cost, ~1.7 ms per agent tool call and ~29 ms per MCP dispatch.
- Make McpToolPolicy a relabelled ToolBudget and derive PolicyConfiguration's
  ceilings from the shared constants. The literal 20 matched MAX_TOOL_RESULTS by
  coincidence, so raising it would have made MCP reject calls its own advertised
  schema declared valid.
- Map PermissionError to a model-visible error, and log a tool exception that
  escapes call_tool instead of discarding it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Updated the migration function reference in the readonly composition test to use the correct path.
- Changed the method for building the topology repository in the same test.
- Modified the dependency direction checks to remove unnecessary imports and clarify allowed package relationships.
- Introduced a new script for smoke-testing package installations in isolated environments, ensuring that only the required packages and their dependencies are installed.
- Cleaned up the `uv.lock` file by removing unused dependencies and ensuring correct package specifications.
Four places let something other than the caller's own identity decide what the
caller could read or write.

- Long-term memory checked a write with visible_to, which compares only the
  fields the scope keys on. A TENANT-scoped write was therefore unconstrained
  beyond the tenant: any member could publish a tenant-wide fact stamped with a
  colleague's user_id. Erasure is by owner, so the colleague's erasure would
  delete it and the author's would miss it. A write may still leave a field
  unset, which is how a genuinely tenant-wide fact is recorded.
- Graph reads derived tenant visibility from the candidate batch's own
  allowlists. A batch holding only tenant-scoped nodes produced empty lists and
  denied them to a fully authorized reader, while one readable document
  alongside them unlocked every one. It now asks the principal's own grants,
  and only when the batch contains such a record.
- FastMCP defaults mask_error_details off, so any exception escaping a tool
  reached the client as "Error calling tool 'x': {exc}" -- enough to leak a
  connection URL or a server path. The HTTP route already masked; the MCP
  transport now agrees. A ToolError we raise ourselves is still delivered whole.
- Resolving the MCP principal as a call argument put it before call_tool, so a
  token probing another tenant left no audit record at all. The attempt and its
  refusal are now both recorded against the token's subject.

Also return an unauthorized HTTP tool call as its own status instead of letting
the generic handler report an authorization decision as a 500.

Coverage was below its own 90% gate at 88.04%; these tests bring it to 90.08%.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four resource and durability defects, each on a path that only shows up under
load or on someone else's filesystem.

- Kill an isolated subprocess on every exit path. join() with a timeout returns
  whether or not the process ended, so a parse that stopped heartbeating was
  joined and then abandoned while still running. Only cancellation killed, and
  the document stage retries five times, so each hung parse piled another live
  interpreter onto the worker.
- Bound concurrent document retirement. retire_removed opens a session per call
  and every removal went in flight at once, so a scan detecting a few hundred
  removals overran the control pool, timed out, and marked the task FAILED
  after every document had already been published.
- Resolve the shared ingestion batch defaults without building a Temporal
  deployment. Reading two integers validated the whole config, so a stray
  HARBORRAG_TEMPORAL_TARGET without TLS, or a worker capacity exceeding the
  database pool, failed an inline ingestion that connects to neither.
- Separate sqlite_url from prepare_sqlite_database. Building a URL created
  directories and reset the mode of whichever parent the operator had chosen,
  stripping group and other access from a shared directory or raising
  PermissionError on one this process does not own. A directory we create is
  still 0700 and the database file is still 0600.

Behaviour change: a pre-existing parent directory now keeps its mode. Callers
that relied on sqlite_url creating the file must call prepare_sqlite_database.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
User accounts are deliberately not enabled: Principal.__post_init__ pins
user_id to DEFAULT_USER for every caller, and a test pins that decision. The
surrounding documentation described the opposite, which is the part worth
fixing without reopening the decision.

Five docstrings claimed a per-person boundary that does not exist. The
conversation and memory routes said a caller "cannot ask about or erase anyone
else's" data; in HMAC mode any subject holding a token for a tenant can list,
read, rename, delete and erase every conversation and USER-scoped memory in
it. They now say so, and name the tenant as the boundary that does hold.

HARBORRAG_AUTH_USER_ID_CLAIM is resolved by the verifier and then discarded.
It is kept as the seam the user-accounts work will use, now marked as having
no effect today rather than described as an isolation control.

The capacity tiers have the same root. capacity_scope_for reads the pinned
user_id, so the user tier is a per-tenant limit: two subjects key one bucket
and one of them holding several long-lived streams exhausts it for the other.
The "identical keys collapse" tests describe a scope no API caller reaches,
because user_id never equals the subject. Added the missing test that drives
capacity_scope_for from a real Principal and pins what actually happens.

No behaviour changes here. Per-user ownership and a real per-user capacity
tier both need the user-accounts decision, not a configuration change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Binding a tenant only where the schema declares the property turns an omitted
tenant_id into a silent grant rather than a loud failure: ToolSpec.input_schema
defaults to a bare {"type": "object"}, so a tool added without a schema would
receive the model's raw arguments unscoped and nothing would complain. The
catalog test names the three tools that are legitimately tenant-free, with the
reason each one is, and fails the day a fourth appears. A second test holds the
other half of their safety: all three forbid additional properties.

Also removed a sentence added in the previous commit claiming callers can set
limit_scope to principal. limit_scope is only ever written into
HarborRateLimitError.details to report which tier was exceeded; it is not an
input. Replacing prose that promised what the code does not do was the point of
that commit, so it should not have introduced more.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Security definitions first. Configuration validation, model-error sanitisation
and text redaction each kept their own list of credential-shaped field names,
and they had already drifted: only redaction knew private_key, none knew
passwd. They now share SENSITIVE_FIELD_TOKENS.

Consolidating surfaced three real gaps, each fixed:
- A compound name was compared against the whole field name, so api_key was
  caught and my_api_key was not. A prefix was all it took to store a raw key.
  Compound entries now match at token boundaries.
- redact_secrets only handled "authorization: Bearer x", so a Basic credential
  or a raw key under that header went to the log intact.
- "token" also means an LLM accounting unit, so {"token_budget": 8000} was
  unstorable while max_tokens passed on the plural. Counting qualifiers now
  exempt it, and "tokens=5" is no longer rewritten to "token=<redacted>".

Behaviour change: private_key, passwd and any compound spelling of an existing
name are now refused in stored configuration. A deployment holding one of those
raw must move it behind a secret:// reference.

Contracts that only held by accident:
- URLPolicy.validate let urlparse's bare ValueError escape for a malformed URL,
  so a caller mapping URLPolicyError to a 400 got an unhandled 500 and one
  using it as a deny path treated the URL as acceptable.
- finish_reason was str | FinishReason, which a smart union always resolved to
  str, so comparing against the enum was False for every response. It is the
  enum now, with a lenient parse so an unfamiliar provider value becomes
  UNKNOWN rather than failing a call that succeeded.
- ConversationIdentity and AgentRunIdentity accepted blank isolation keys and
  pooled every such caller into one "" bucket. principal_id stays exempt: it is
  audit provenance, excluded from comparison, and callers legitimately leave it
  blank when building an identity purely as a lookup key.
- _deep_freeze recognised FrozenMetadata by comparing __name__ and __module__,
  so moving or subclassing it silently resumed re-wrapping.
- The agent evidence marker was guarded by assert, which -O erases.

Not fixed, needs a decision: SettingsRepositoryPort is not tenant-scoped even
though its provider is tenant-scoped and WorkspaceSettings carries a tenant_id,
so every tenant shares one document. Closing it needs a unique constraint on
workspace_settings.tenant_id and a migration to split the existing row, so the
port now documents the gap rather than half-closing it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Recall deduped on content_hash, which is salted with the scope. One sentence
stored at both SESSION and USER hashed differently, so both survived: the
budget was spent twice and the reader saw one fact presented as two. Rows with
no hash already deduped on normalized content, so the rule also depended on
whether a hash happened to exist. It is the normalized content either way now.

Add-only extraction built its hash set from at most fifty rows per scope with
no ordering guarantee, so past fifty memories in a scope the "a retry is a
no-op" guarantee lapsed and restatements accumulated. It now asks about the one
fact being written; the semantic check stays as the backstop for differently
worded restatements.

The in-memory working store evicted an expired entry only when that exact owner
was read again, so a run that ended without one more read kept its scratch
state for the life of the process. Writes now sweep.

Reading a conversation raised on a single unparseable citations or tool-calls
column, making the whole history unreadable. A message with unreadable metadata
is worth more than no conversation, so the metadata is dropped and the text
kept.

snapshot(query=...) returned () when no long-term tier was configured while
search() raised, so callers could not tell "nothing stored" from "not
configured". Both raise now.

Three copies of the rule mapping a memory owner onto its conversation identity,
comment and all, are now one shared function. Removed the unused record,
remember and forget aliases.

Behaviour changes: recall returns fewer rows where the same fact was filed
under two scopes, and snapshot now raises instead of returning empty.

Refuted while checking, so deliberately unchanged: the rolling summary does
trigger for a window of short messages. Reproduced with thirty ten-token
messages against a twelve-message window -- the catch-up path writes a summary
covering the older turns and LAST_COVERED_KEY is read back on the next build.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A resume restored only logical_model and took graph_search, max_steps,
max_total_tokens and timeout_seconds from whoever called resume. Resuming with
graph_search=True handed the model graph tools that the earlier steps of the
same transcript never had, and a different budget silently re-budgeted a run
already in progress. Those options are now checkpointed and restored. The
checkpoint state is a JSON column, so no migration: a field the checkpoint does
not carry predates this and falls back to the caller's value, which is what the
run effectively had before.

A terminal checkpoint that failed to save was suppressed without a trace, so
the run stayed RUNNING under a live lease until it expired and a resume
meanwhile failed with a misleading conflict. Still best effort, now logged.

Cancellation checkpointing suppressed BaseException, which also swallowed a
KeyboardInterrupt or SystemExit raised inside the shielded persist. Narrowed to
Exception.

The grounding instruction had lost its subject in an edit: "...alter a marker.
distinguish any inference." The clause telling the model to separate evidence
from inference is restored.

The repeat-detection key was built by the same three-line expression at each of
the three call sites, so the key a call was admitted under, executed under and
rejected under could drift while every site still looked right alone.

Authoritative retrieval doubled its window on each pass, re-reading the whole
window from the start because a larger top_k re-ranks globally rather than
paging. It now widens to what the observed acceptance rate implies, reaching
the same window in about one extra read instead of three.

Removed identity_rewrite and keep_top, which only their own tests called.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
FalkorDB's generic graph repository opens one client on one graph and has never
honoured tenant_isolation; only the topology and knowledge repositories do.
Accepting the setting and ignoring it is the dangerous half: an operator who
switched it on believed tenants sat in separate graphs while every write still
went to the shared one, separated by predicate alone. Construction now refuses.

Hybrid search caps its candidate set because fusion holds both lanes in memory,
then sliced the requested page out of it. A page past the cap returned the empty
slice, reporting "no more results" for a page never looked at. It now raises a
capability error naming the ceiling.

delete_records was the one Qdrant operation that skipped the spec lookup, so
deleting from an unknown index produced a raw provider 404 instead of
HarborStorageNotFoundError.

A provider signalled a missing optional extra by raising ImportError with an
exact wording the registry matched by regular expression, so a provider wording
it differently, or an ImportError from a transitive import, fell through as a
bare traceback. There is a MissingOptionalDependencyError now; the message is
unchanged and the regex remains as the compatibility path. cryptography and
alembic were imported unguarded and so never reached that path at all.

Behaviour change: a deployment with falkordb tenant_isolation set on the generic
graph repository now fails at construction instead of starting up unisolated.

Investigated and deliberately not changed: driver exceptions crossing the
storage port. Twenty-two tests across S3, redis and Qdrant assert that an
unmapped provider error is re-raised as itself, so this is a tested contract
rather than an oversight. Translating it is a design decision, not a fix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
vector_search's score_threshold filtered on score, which the settings comment
and the output schema both say must never be thresholded: on the hybrid lane it
is rank-fusion arithmetic whose top hit sits near 1.0 however poor the match. A
request for high-quality results therefore returned that hit regardless of
quality and dropped good matches further down. It filters on relevance now,
falling back to score only for lanes that report no relevance at all, and the
parameter says so to the model.

Three tool modules imported their contract types from the SDK facade rather
than from reader_contracts where they are defined, so importing a stateless
tool pulled in HarborRAG, chat, memory and execution -- and made a cycle
inevitable the moment the SDK referenced tools at runtime.

The reference store swept expired handles on every issue by walking every
entry, which at the hundred-thousand ceiling meant a full scan under the lock
per tool call. Entries share one TTL and re-issuing does not extend it, so
insertion order is expiry order and the first live entry ends the sweep.

The settings module claimed pydantic-settings was an optional extra imported
lazily by CompositionRoot.production(). It is a required dependency imported
eagerly, which matters to anyone deciding where a new import may go.

Behaviour change: a vector_search call with a non-zero score_threshold now
returns results chosen by measured relevance, so a hybrid-lane query returns a
different set than before.

Left in place after investigating: the hasattr guard in the Temporal worker.
It is duck-typing for settings objects without graph-build configuration, not a
dead branch, and removing it pulls an optional projection queue into a test that
deliberately does not exercise it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The trail recorded a tool, a principal and an argument digest, but not the
tenant -- the first question anyone asks of it. Both events now carry the
canonical stripped tenant, the same value that drove policy, validation and the
grant check. The arguments stay a digest so the log never becomes a copy of the
payload.

Its default path was relative, so an MCP client launching the stdio server from
a directory of its choosing put the trail somewhere different each time, or in
a directory the process could not make owner-only, which failed every tool
call. A relative path is now anchored at the home directory, where the
ownership rules the writer enforces can actually hold. An absolute path is
untouched.

Each durable event opens, writes and fsyncs under a lock, twice per call, and
dispatch runs inline in the transport's loop. The writes move to a thread;
fsync stays, because an audit trail that is fast and lossy is worth less than
one that is slow and complete.

The audit counted status == "error" as a failure while the MCP handler raised
only on ok is False, so the two could disagree about one result. One predicate
decides for both now.

Reading the cached configuration deep-copied the whole document, and a
tools/list resolves every tool twice and asks for the policy once per tool --
roughly forty copies per listing. Internal readers use the cached object and
still copy what they hand back.

Documented rather than changed: FastMCP builds its tool table at startup, so
tools/list cannot vary per tenant. A tool a tenant has disabled is advertised
and then refused at call time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PATCH /api/v1/sources/{id} took status as an unvalidated string and wrote it
straight into a field whose domain type is a Literal on a plain dataclass,
which is a static annotation and no runtime check. {"status": "banana"}
persisted, and any scheduler keyed on active/paused/error then mishandled the
row. The input now carries the domain type, so an unknown value is a 422.

The workspace settings document is schemaless, so a webhook URL or a key can
end up in it, and it was returned verbatim to the lowest role that can reach
the route. It is redacted now, which is what the TODO there asked for.

The ingestion router held the server deadline at request scope, which the
capacity module's own docstring says must not wrap a streaming body: it stayed
armed across the SSE response and hard-cancelled the stream mid-flight with no
terminal error frame. Function scope, like the chat and agent routers. The
capacity lease is unaffected and still runs to the last frame.

The unified completions endpoint opened a session with kind "chat" whatever the
mode, so an agent run was filed as a chat and never appeared under
GET /v1/conversations?kind=agent.

manifest_id and generation_id were accepted by the legacy ingestion schema and
never forwarded, so a client that set one believed it had pinned a manifest
while nothing had. They are marked deprecated in the schema, which is what the
endpoint can honestly say about them.

Behaviour changes: an unknown source status is now rejected rather than stored,
and settings values under credential-shaped keys come back redacted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The route it is served from carries a reader-role dependency, and the document
is schemaless, so a webhook URL or a key stored in it was readable by the
lowest role that can reach the endpoint. The redaction the TODO there asked for
now runs. Split from the previous commit, where the edit was lost to a failed
assertion earlier in the same script.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Running every package in a single pytest process failed twelve tests that pass
per-package. None was a product defect; all three causes were test isolation.

An app fixture set HARBORRAG_CONTROL_DB_URL to a tmp-path database through
monkeypatch, while a sibling autouse fixture snapshotted os.environ to undo
writes that production code makes directly. The snapshot was taken after the
set, and its teardown can run after monkeypatch has already undone it, so it
put the tmp path back permanently and leaked one test's database into every
suite that followed. The snapshot fixture now runs first. The two runtime
checkpoint tests that read that variable also unset it themselves, since what
they assert is that no DSN is configured.

The process-logging tests restored the shared "harborrag" logger afterwards but
not before. configure_logging is deliberately idempotent, so a handler left by
anything that ran earlier made the call a no-op and the test's own stream
received nothing. The fixture now resets before the test too.

The Temporal sandbox tests failed once fastmcp had been imported into the same
process: the sandbox re-imports a workflow's dependency chain, and beartype
does not survive it -- its claw submodule is left partially initialized. A
worker never imports fastmcp, so this is an artefact of sharing an interpreter.
A shared fixture passes beartype through, which is what passthrough is for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Introduced new session management endpoints for listing, creating, renaming, and deleting sessions.
- Added support for session history retrieval and pagination.
- Enhanced API schemas to include new session-related fields and constraints.
- Implemented query scope validation to ensure requests are properly classified.
- Created TypeScript client methods for session operations, including error handling and request cancellation.
- Added comprehensive tests for session management and query scope functionality.
- Updated OpenAPI documentation to reflect new endpoints and parameters.
- Updated the HarborRAG class to lazily initialize memory and chat services.
- Removed unnecessary imports and cleaned up the code structure.
- Deleted unused tool modules and refactored related tests to reflect changes.
- Adjusted dependencies in pyproject.toml and uv.lock for better package management.
- Enhanced test coverage for reader applications and retrieval factory.
- Ensured proper closure of services in tests to prevent resource leaks.
- Added HarborAuthorizationUnavailableError to handle unavailable permissions in retrieval.
- Updated RetrievalPermissions to raise an error instead of returning empty results when authorization fails.
- Improved handling of tenant shared corpus mode in retrieval permissions.
- Refactored service closure logic to handle exceptions more gracefully.
- Enhanced summary factory to support tenant-specific processing policies.
- Introduced new environment handling for MCP server configuration.
- Added support for hashed, tenant-bound API keys in MCP server.
- Implemented tests for shared reader access and API key verification.
- Updated deployment scripts to streamline environment variable management.
@coderabbitai

coderabbitai Bot commented Sep 18, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: fd5d0e7a-2539-4516-a7e3-a24dd94f798f


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@HuntyAmour
HuntyAmour force-pushed the feat/memory-chat-agent branch from 9c851c3 to 7396fec Compare September 18, 2026 14:41
@HuntyAmour
HuntyAmour merged commit 237a390 into dev/v2.0.0 Sep 18, 2026
23 checks passed
@HuntyAmour
HuntyAmour deleted the feat/memory-chat-agent branch September 18, 2026 15:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant