Skip to content

Kontext - an AI agent memory plugin for KurrentDB#5567

Draft
shaan1337 wants to merge 1 commit intomasterfrom
shaan1337/kontext
Draft

Kontext - an AI agent memory plugin for KurrentDB#5567
shaan1337 wants to merge 1 commit intomasterfrom
shaan1337/kontext

Conversation

@shaan1337
Copy link
Member

@shaan1337 shaan1337 commented Mar 25, 2026

Adds the Kontext plugin - an embedded agent memory system for KurrentDB that gives AI agents durable, searchable memory backed by KurrentDB events.

Configuration

Only one setting — everything else is fixed with sensible defaults:

KurrentDB:
  Kontext:
    Enabled: true

Data stored under {index}/kontext/. Memory stream is $kontext-memory.

Connecting Claude Code

Add .mcp.json to your project root:

{
  "mcpServers": {
    "kontext": {
      "type": "http",
      "url": "https://localhost:2113/mcp/kontext"
    }
  }
}

How it works

The plugin subscribes to $all and indexes every event into a local SQLite database using hybrid text (BM25) + vector (embedding) search. Agents connect via MCP over HTTP and can search events, retain synthesized facts, and recall them in future sessions. All ML inference (sentence embeddings + cross-encoder re-ranking) runs on CPU via ONNX Runtime — no LLM calls needed for indexing.

Embeds Kurrent.Kontext directly in the KurrentDB server process,
exposing an MCP HTTP endpoint at /mcp/kontext for AI agent access.

Plugin components:
- KontextPlugin: SubsystemsPlugin, enabled via KurrentDB:Kontext:Enabled
- KontextClient: IKontextClient using ISystemClient for reads/writes
  and SystemClient.Subscriptions for $all subscription
- KontextStreamAccessChecker: post-filters search results against
  stream ACLs via IAuthorizationProvider. Unauthorized events are
  redacted (data/metadata stripped, AccessDenied flag set)

Configuration is minimal — only Enabled is user-configurable. Data
files are stored under {index}/kontext/. Memory stream is $kontext-memory.
MCP endpoint is /mcp/kontext with HTTP Streamable transport.

Tests: 21 passing (KontextClientTests, KontextPluginTests,
EndToEndTests, McpEndpointTests)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@shaan1337 shaan1337 requested a review from a team as a code owner March 25, 2026 19:08
@qodo-code-review
Copy link
Contributor

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: ci/github/scan-vulnerabilities

Failed stage: Scan for Vulnerabilities [❌]

Failed test name: ""

Failure summary:

The action failed during dotnet restore because the solution src/KurrentDB.sln references project
files that do not exist at the expected paths.
- MSB3202: Missing project file
libs/Kurrent.Kontext/src/Kurrent.Kontext/Kurrent.Kontext.csproj (reported from
/usr/share/dotnet/sdk/10.0.201/NuGet.targets(519,5)).
- MSB3202: Missing project file
libs/Kurrent.Kontext/src/Kurrent.Kontext.Models/Kurrent.Kontext.Models.csproj (reported from
/usr/share/dotnet/sdk/10.0.201/NuGet.targets(519,5)).
Because these .csproj files were not found,
the restore step exited with code 1, causing the GitHub Action to fail.

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

135:  dotnet-install: The remote and local file sizes are equal.
136:  dotnet-install: Installed version is 10.0.201
137:  dotnet-install: Adding to current process PATH: `/usr/share/dotnet`. Note: This change will be visible only when sourcing script.
138:  dotnet-install: Note that the script does not resolve dependencies during installation.
139:  dotnet-install: To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install, select your operating system and check the "Dependencies" section.
140:  dotnet-install: Installation finished successfully.
141:  ##[group]Run cd src
142:  �[36;1mcd src�[0m
143:  �[36;1mdotnet restore�[0m
144:  �[36;1mdotnet list package --vulnerable --include-transitive | tee vulnerabilities.txt�[0m
145:  �[36;1m! cat vulnerabilities.txt | grep -q "has the following vulnerable packages"�[0m
146:  shell: /usr/bin/bash -e {0}
147:  env:
148:  DOTNET_ROOT: /usr/share/dotnet
149:  ##[endgroup]
150:  /usr/share/dotnet/sdk/10.0.201/NuGet.targets(519,5): error MSB3202: The project file "/home/runner/work/KurrentDB/KurrentDB/libs/Kurrent.Kontext/src/Kurrent.Kontext/Kurrent.Kontext.csproj" was not found. [/home/runner/work/KurrentDB/KurrentDB/src/KurrentDB.sln]
151:  /usr/share/dotnet/sdk/10.0.201/NuGet.targets(519,5): error MSB3202: The project file "/home/runner/work/KurrentDB/KurrentDB/libs/Kurrent.Kontext/src/Kurrent.Kontext.Models/Kurrent.Kontext.Models.csproj" was not found. [/home/runner/work/KurrentDB/KurrentDB/src/KurrentDB.sln]
152:  ##[error]Process completed with exit code 1.
153:  Post job cleanup.

@qodo-code-review
Copy link
Contributor

Review Summary by Qodo

Add KurrentDB.Plugins.Kontext — embedded agent memory plugin with MCP support

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Adds KurrentDB.Plugins.Kontext embedded agent memory plugin with MCP HTTP endpoint
• Implements IKontextClient using internal ISystemClient for reads/writes/subscriptions
• Provides KontextStreamAccessChecker for post-filtering search results against stream ACLs
• Includes 21 passing tests covering client operations, MCP endpoint, and end-to-end workflows
Diagram
flowchart LR
  Agent["Agent (Claude Code)"]
  MCP["MCP HTTP Endpoint<br/>/mcp/kontext"]
  Auth["Auth Middleware"]
  Plugin["KontextPlugin"]
  Client["KontextClient"]
  SystemClient["ISystemClient"]
  AccessChecker["KontextStreamAccessChecker"]
  SQLite["SQLite Database<br/>FTS5 + Vector"]
  
  Agent -->|"JSON-RPC over POST"| MCP
  MCP --> Auth
  Auth --> Plugin
  Plugin --> Client
  Plugin --> AccessChecker
  Client --> SystemClient
  AccessChecker --> SystemClient
  Plugin --> SQLite
Loading

Grey Divider

File Changes

1. src/KurrentDB.Plugins.Kontext/KontextPlugin.cs ✨ Enhancement +57/-0

Main plugin entry point with service configuration

src/KurrentDB.Plugins.Kontext/KontextPlugin.cs


2. src/KurrentDB.Plugins.Kontext/KontextClient.cs ✨ Enhancement +96/-0

IKontextClient implementation using ISystemClient

src/KurrentDB.Plugins.Kontext/KontextClient.cs


3. src/KurrentDB.Plugins.Kontext/KontextStreamAccessChecker.cs ✨ Enhancement +25/-0

Stream ACL enforcement for search result filtering

src/KurrentDB.Plugins.Kontext/KontextStreamAccessChecker.cs


View more (15)
4. src/KurrentDB.Plugins.Kontext.Tests/KontextClientTests.cs 🧪 Tests +252/-0

Comprehensive tests for read, write, subscribe operations

src/KurrentDB.Plugins.Kontext.Tests/KontextClientTests.cs


5. src/KurrentDB.Plugins.Kontext.Tests/KontextPluginTests.cs 🧪 Tests +73/-0

Tests for plugin configuration and path resolution

src/KurrentDB.Plugins.Kontext.Tests/KontextPluginTests.cs


6. src/KurrentDB.Plugins.Kontext.Tests/McpEndpointTests.cs 🧪 Tests +161/-0

Tests for MCP HTTP endpoint initialization and tools

src/KurrentDB.Plugins.Kontext.Tests/McpEndpointTests.cs


7. src/KurrentDB.Plugins.Kontext.Tests/EndToEndTests.cs 🧪 Tests +56/-0

End-to-end write/read round trip and binary event handling

src/KurrentDB.Plugins.Kontext.Tests/EndToEndTests.cs


8. src/KurrentDB.Plugins.Kontext.Tests/TestEnvironmentWireUp.cs 🧪 Tests +17/-0

Test environment initialization and teardown configuration

src/KurrentDB.Plugins.Kontext.Tests/TestEnvironmentWireUp.cs


9. src/KurrentDB.Plugins.Kontext/README.md 📝 Documentation +112/-0

Documentation for plugin configuration and MCP tools

src/KurrentDB.Plugins.Kontext/README.md


10. src/KurrentDB.Plugins.Kontext/KurrentDB.Plugins.Kontext.csproj ⚙️ Configuration changes +24/-0

Project file with ModelContextProtocol.AspNetCore dependency

src/KurrentDB.Plugins.Kontext/KurrentDB.Plugins.Kontext.csproj


11. src/KurrentDB.Plugins.Kontext.Tests/KurrentDB.Plugins.Kontext.Tests.csproj ⚙️ Configuration changes +35/-0

Test project configuration with TUnit and testing dependencies

src/KurrentDB.Plugins.Kontext.Tests/KurrentDB.Plugins.Kontext.Tests.csproj


12. src/KurrentDB/ClusterVNodeHostedService.cs ✨ Enhancement +1/-0

Register KontextPlugin in subsystems plugin loader

src/KurrentDB/ClusterVNodeHostedService.cs


13. src/KurrentDB/KurrentDB.csproj ⚙️ Configuration changes +1/-0

Add KurrentDB.Plugins.Kontext project reference

src/KurrentDB/KurrentDB.csproj


14. src/Directory.Packages.props Dependencies +1/-0

Add ModelContextProtocol.AspNetCore package version

src/Directory.Packages.props


15. src/KurrentDB/logconfig.json ⚙️ Configuration changes +1/-0

Configure ModelContextProtocol logging level to Warning

src/KurrentDB/logconfig.json


16. .gitmodules Dependencies +3/-0

Add Kurrent.Kontext git submodule reference

.gitmodules


17. libs/Kurrent.Kontext Dependencies +1/-0

Kurrent.Kontext submodule commit reference

libs/Kurrent.Kontext


18. src/KurrentDB.sln Additional files +398/-0

...

src/KurrentDB.sln


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Mar 25, 2026

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (2) 📎 Requirement gaps (0) 📐 Spec deviations (0)

Grey Divider


Action required

1. ToolkitTestEnvironment.Initialize() missing assembly 📘 Rule violation ⛯ Reliability
Description
The new test wire-up does not pass context.Assembly to
ToolkitTestEnvironment.Initialize/Reset, which breaks the required standardized per-assembly
test environment lifecycle. This can cause unreliable test setup/cleanup when multiple test
assemblies run under the shared toolkit.
Code

src/KurrentDB.Plugins.Kontext.Tests/TestEnvironmentWireUp.cs[R10-16]

+	[Before(Assembly)]
+	public static ValueTask BeforeAssembly(AssemblyHookContext context) =>
+		ToolkitTestEnvironment.Initialize();
+
+	[After(Assembly)]
+	public static ValueTask AfterAssembly(AssemblyHookContext context) =>
+		ToolkitTestEnvironment.Reset();
Evidence
PR Compliance ID 1 requires Before(Assembly)/After(Assembly) hooks to call
ToolkitTestEnvironment.Initialize(context.Assembly) and
ToolkitTestEnvironment.Reset(context.Assembly). The added file defines the hooks but calls
ToolkitTestEnvironment.Initialize() and ToolkitTestEnvironment.Reset() without using
context.Assembly.

CLAUDE.md
src/KurrentDB.Plugins.Kontext.Tests/TestEnvironmentWireUp.cs[10-16]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`TestEnvironmentWireUp` does not call `ToolkitTestEnvironment.Initialize(context.Assembly)` / `Reset(context.Assembly)` as required.

## Issue Context
The repository test infrastructure relies on assembly-scoped initialization/reset to avoid cross-test-project interference.

## Fix Focus Areas
- src/KurrentDB.Plugins.Kontext.Tests/TestEnvironmentWireUp.cs[10-16]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. CI misses submodules 🐞 Bug ⛯ Reliability
Description
CI uses actions/checkout without enabling submodule checkout, but the solution now includes
libs/Kurrent.Kontext as a git submodule and directly ProjectReferences its csproj, causing
restore/build failures in CI and for developers without git submodule update --init.
Code

src/KurrentDB.Plugins.Kontext/KurrentDB.Plugins.Kontext.csproj[R19-22]

+		<ProjectReference Include="..\KurrentDB.Common\KurrentDB.Common.csproj" />
+		<ProjectReference Include="..\KurrentDB.Core\KurrentDB.Core.csproj" />
+		<ProjectReference Include="..\..\libs\Kurrent.Kontext\src\Kurrent.Kontext\Kurrent.Kontext.csproj" />
+	</ItemGroup>
Evidence
The plugin project references
..\..\libs\Kurrent.Kontext\src\Kurrent.Kontext\Kurrent.Kontext.csproj (submodule path) and the
solution adds Kurrent.Kontext projects, but GitHub workflows check out the repo without
submodules, so the referenced paths will not exist during dotnet restore/build.

src/KurrentDB.Plugins.Kontext/KurrentDB.Plugins.Kontext.csproj[18-22]
.gitmodules[1-3]
.github/workflows/build-reusable.yml[61-63]
.github/workflows/common.yml[26-29]
src/KurrentDB.sln[195-202]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The repo now depends on a git submodule (`libs/Kurrent.Kontext`) at build time via `ProjectReference`, but CI workflows check out the repository without fetching submodules. This causes missing project files during restore/build.

### Issue Context
Workflows using `actions/checkout@v4` must set `submodules: recursive` (or at least `true`) for submodules to be present.

### Fix Focus Areas
- .github/workflows/common.yml[21-73]
- .github/workflows/build-reusable.yml[17-97]
- .github/workflows/build-container-reusable.yml[11-97]

### Suggested change
Update each `actions/checkout@v4` step to include:
```yaml
with:
 submodules: recursive
```
If you intentionally do not want submodules in some jobs, then remove the `ProjectReference` dependency and consume `Kurrent.Kontext` as a NuGet package or vendored source instead.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. WriteAsync lacks authorization check 📘 Rule violation ⛨ Security
Description
KontextClient.WriteAsync writes to an arbitrary stream via systemClient.Writing.WriteEvents
without any prior authorizationProvider.CheckAccessAsync verification. If reachable from
user-driven MCP tools, this could allow unauthorized writes or side effects before access is
validated.
Code

src/KurrentDB.Plugins.Kontext/KontextClient.cs[R92-95]

+	public async Task WriteAsync(string stream, string eventType, ReadOnlyMemory<byte> data) {
+		var evt = new Event(Guid.NewGuid(), eventType, isJson: true, data.ToArray());
+		await systemClient.Writing.WriteEvents(stream, [evt]);
+	}
Evidence
PR Compliance ID 7 requires protected operations to verify access via
authorizationProvider.CheckAccessAsync(...) before performing side effects. The new WriteAsync
method performs the write side effect immediately and contains no authorization check.

CLAUDE.md
src/KurrentDB.Plugins.Kontext/KontextClient.cs[92-95]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`KontextClient.WriteAsync` performs a stream write without an explicit authorization check.

## Issue Context
If this method can be invoked from user-authenticated contexts (e.g., MCP endpoint tools), it should validate write permissions (or restrict writes to a fixed stream) before calling `WriteEvents`.

## Fix Focus Areas
- src/KurrentDB.Plugins.Kontext/KontextClient.cs[92-95]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Unvalidated read range 🐞 Bug ✓ Correctness
Description
KontextClient.ReadAsync can throw or misbehave when to < eventNumber or eventNumber < -1
because limit can become non-positive and StreamRevision.FromInt64 throws for values < -1, which
is not caught and will crash callers.
Code

src/KurrentDB.Plugins.Kontext/KontextClient.cs[R52-58]

+	public async IAsyncEnumerable<EventResult> ReadAsync(string stream, long eventNumber, long? to = null) {
+		var limit = to.HasValue ? (to.Value - eventNumber + 1) : 1;
+
+		IAsyncEnumerable<ResolvedEvent> source;
+		try {
+			source = systemClient.Reading.ReadStreamForwards(stream, StreamRevision.FromInt64(eventNumber), limit);
+		} catch (ReadResponseException.StreamNotFound) {
Evidence
StreamRevision.FromInt64 only treats -1 as End; values less than -1 throw
(Convert.ToUInt64). Also, KurrentDB's ReadStream path casts maxCount to ulong; a negative
limit becomes a very large value, risking large reads/hangs rather than a bounded range read.

src/KurrentDB.Plugins.Kontext/KontextClient.cs[52-58]
src/KurrentDB.Core/Services/Transport/Common/StreamRevision.cs[11-18]
src/KurrentDB.Core/Bus/Extensions/PublisherReadExtensions.cs[132-170]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`KontextClient.ReadAsync(stream, eventNumber, to)` doesn't validate its inputs. Invalid values can throw (negative eventNumber < -1) or cause pathological reads (negative limit cast to ulong).

### Issue Context
This method is part of the embedded Kontext client surface and may be called from MCP tools / library code. Defensive input validation prevents hard-to-debug failures.

### Fix Focus Areas
- src/KurrentDB.Plugins.Kontext/KontextClient.cs[52-58]

### Suggested fix
Add guards before calling into `systemClient`:
- If `eventNumber < -1`, throw `ArgumentOutOfRangeException` (or yield break).
- If `to.HasValue` and `to.Value < eventNumber`, yield break or throw `ArgumentException`.
- Ensure computed `limit` is > 0.

Example:
```csharp
if (eventNumber < -1)
 throw new ArgumentOutOfRangeException(nameof(eventNumber));
if (to is { } end && end < eventNumber)
 throw new ArgumentException("'to' must be >= eventNumber", nameof(to));
var limit = to.HasValue ? (to.Value - eventNumber + 1) : 1;
if (limit <= 0) yield break;
```

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

5. MCP test reads twice 🐞 Bug ⛯ Reliability
Description
Mcp_Endpoint_Exposes_All_Tools reads the same HttpResponseMessage.Content twice, which can yield
empty content or throw depending on the content stream implementation, making the test flaky and
possibly masking real protocol issues.
Code

src/KurrentDB.Plugins.Kontext.Tests/McpEndpointTests.cs[R121-127]

+
+		var (_, sessionId) = await ReadMcpResponse(initResponse, ct);
+
+		// The initialize response already contains capabilities with tools listed.
+		// Verify tools are available by checking the capabilities in the init response.
+		var (initBody, _) = await ReadMcpResponse(initResponse, ct);
+
Evidence
The test calls ReadMcpResponse(initResponse, ct) to extract the session id and then calls it again
to get the body; both calls invoke response.Content.ReadAsStringAsync. While StringContent is
buffered, streaming/SSE responses (which MCP can use) are commonly non-rewindable, so this pattern
is fragile.

src/KurrentDB.Plugins.Kontext.Tests/McpEndpointTests.cs[121-127]
src/KurrentDB.Plugins.Kontext.Tests/McpEndpointTests.cs[63-65]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The MCP tools test reads the same response body twice.

### Fix Focus Areas
- src/KurrentDB.Plugins.Kontext.Tests/McpEndpointTests.cs[121-138]

### Suggested fix
Read once and reuse the result:
```csharp
var (initBody, sessionId) = await ReadMcpResponse(initResponse, ct);
```
Then use `initBody` in the else-branch and `sessionId` for the tools/list request.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@shaan1337 shaan1337 marked this pull request as draft March 25, 2026 19:08
@shaan1337 shaan1337 changed the title Add KurrentDB.Plugins.Kontext — embedded agent memory plugin Kontext - an agent memory plugin for KurrentDB Mar 25, 2026
@shaan1337 shaan1337 changed the title Kontext - an agent memory plugin for KurrentDB Kontext - an AI agent memory plugin for KurrentDB Mar 25, 2026
Comment on lines +10 to +16
[Before(Assembly)]
public static ValueTask BeforeAssembly(AssemblyHookContext context) =>
ToolkitTestEnvironment.Initialize();

[After(Assembly)]
public static ValueTask AfterAssembly(AssemblyHookContext context) =>
ToolkitTestEnvironment.Reset();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. toolkittestenvironment.initialize() missing assembly 📘 Rule violation ⛯ Reliability

The new test wire-up does not pass context.Assembly to
ToolkitTestEnvironment.Initialize/Reset, which breaks the required standardized per-assembly
test environment lifecycle. This can cause unreliable test setup/cleanup when multiple test
assemblies run under the shared toolkit.
Agent Prompt
## Issue description
`TestEnvironmentWireUp` does not call `ToolkitTestEnvironment.Initialize(context.Assembly)` / `Reset(context.Assembly)` as required.

## Issue Context
The repository test infrastructure relies on assembly-scoped initialization/reset to avoid cross-test-project interference.

## Fix Focus Areas
- src/KurrentDB.Plugins.Kontext.Tests/TestEnvironmentWireUp.cs[10-16]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +19 to +22
<ProjectReference Include="..\KurrentDB.Common\KurrentDB.Common.csproj" />
<ProjectReference Include="..\KurrentDB.Core\KurrentDB.Core.csproj" />
<ProjectReference Include="..\..\libs\Kurrent.Kontext\src\Kurrent.Kontext\Kurrent.Kontext.csproj" />
</ItemGroup>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Ci misses submodules 🐞 Bug ⛯ Reliability

CI uses actions/checkout without enabling submodule checkout, but the solution now includes
libs/Kurrent.Kontext as a git submodule and directly ProjectReferences its csproj, causing
restore/build failures in CI and for developers without git submodule update --init.
Agent Prompt
### Issue description
The repo now depends on a git submodule (`libs/Kurrent.Kontext`) at build time via `ProjectReference`, but CI workflows check out the repository without fetching submodules. This causes missing project files during restore/build.

### Issue Context
Workflows using `actions/checkout@v4` must set `submodules: recursive` (or at least `true`) for submodules to be present.

### Fix Focus Areas
- .github/workflows/common.yml[21-73]
- .github/workflows/build-reusable.yml[17-97]
- .github/workflows/build-container-reusable.yml[11-97]

### Suggested change
Update each `actions/checkout@v4` step to include:
```yaml
with:
  submodules: recursive
```
If you intentionally do not want submodules in some jobs, then remove the `ProjectReference` dependency and consume `Kurrent.Kontext` as a NuGet package or vendored source instead.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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