Agent Memory
SDKs

.NET SDK

AgentMemory.Client for .NET 10 — an HTTP implementation of IAgentMemory with dependency-injection helpers and an agent context for the common calls.

AgentMemory.Client is the .NET client. It implements the platform's IAgentMemory interface over the /v1 HTTP API, so code written against the interface runs the same whether memory is in-process or remote. It targets .NET 10 and depends on AgentMemory.Contracts (the shared models) and Microsoft.Extensions.Http.

Install

The client is not published to NuGet. Add it to your solution as a project reference from the Agent Memory source repository:

dotnet add reference /path/to/agent-memory/AgentMemory.Client/AgentMemory.Client.csproj

AgentMemory.Contracts comes along as a transitive project reference.

Configure

using AgentMemory.Client;

builder.Services.AddAgentMemoryClient(
    "https://memory.example.com",
    _ => Task.FromResult<string?>(builder.Configuration["AgentMemory:ApiKey"]));

AddAgentMemoryClient(baseUrl, tokenProvider) registers IAgentMemory as a typed HttpClient (30-second timeout). The token provider is called per request, so it can return a rotating OAuth token as easily as a static API key.

The bearer token and any Idempotency-Key are set per request, never on the shared HttpClient.

The agent context

Most IAgentMemory methods take a TenantExecutionContext (who is calling) and a MemoryAddress (where the memory lives). AgentContext builds both for you from an API key's workspace and fixes the run ID, so everyday calls are one line:

using AgentMemory.Client;
using AgentMemory.Contracts.Abstractions;

var agent = AgentContext.ForKey(
    client,
    tenantId: tenantId,          // shown when the key/tenant was created
    workspaceId: workspaceId,
    userId: "alex",              // optional: the end user, requires "act for users"
    runId: "onboarding-42");     // optional: a new run-… id is minted when omitted

ForKey names no agent unless you pass agentId: — the key's bound agent acts. Tenant and workspace IDs on the client are only used to address requests; the server derives the tenant from the key and checks the workspace against it. An applicationId: argument scopes the context to an application.

With dependency injection, register a scoped context:

builder.Services.AddAgentContext(
    sp => new AgentPrincipal(tenantId, workspaceId, AgentId: "", AgentInstanceId: null, UserId: null,
        Roles: [Roles.Agent], Scopes: []));

AgentPrincipal and Roles are in AgentMemory.Contracts.Security. An empty AgentId means the key's bound agent acts. Each scope gets a fresh run ID unless you pass the optional runId factory.

Remember, recall, forget

Extension methods on IAgentContext (namespace AgentMemory.Contracts.Abstractions) cover the common lifecycle:

// remember
AppendOutcome stored = await agent.AppendAsync(
    "Alex is vegetarian", type: "preference", visibility: MemoryVisibility.User,
    tags: new Dictionary<string, string> { ["facet"] = "diet" },
    idempotencyKey: "onboarding-42:diet");

// recall
MemoryContext context = await agent.SearchAsync("what can Alex eat?");
Console.WriteLine(context.ToPromptBlock());   // render the context as a prompt section

// read
MemoryEntry? entry = await agent.GetAsync(stored.Id);

// share (needs the share capability)
ShareOutcome shared = await agent.ShareAsync(stored.Id, MemoryVisibility.Workspace);

// end the run
await agent.ConsolidateAsync(goal: "Onboard Alex");

// forget (never cascades unless you pass cascade: true)
DeleteOutcome deleted = await agent.DeleteAsync(stored.Id);
ExtensionCalls
AppendAsync(content, type = "decision", visibility = Workspace, tags, idempotencyKey)POST /v1/memory/append (layer Workspace)
SearchAsync(query, options?)POST /v1/memory/retrieve
GetAsync(memoryId)GET /v1/memory/{id}
ShareAsync(memoryId, visibility)POST /v1/memory/{id}/share
DeleteAsync(memoryId, cascade = false)DELETE /v1/memory/{id}
ConsolidateAsync(goal?)POST /v1/memory/consolidate
Address(), Execution()Build the MemoryAddress / TenantExecutionContext for direct client calls.

SearchAsync accepts a RetrieveOptions with every retrieval option: TopN, TokenBudget, Mode, Rerank, MinScore, Raw, IncludeSuperseded, AsOf, AsOfAxis, Temporal, Tags, ExpandSessions, Explain, Overrides.

DeleteOutcome is Deleted, NotFound, ProtectedTenantScope, PolicyDenied or BlockedByDependents. To see whether cleanup is still pending, or which dependents blocked the delete, call the client overload that returns a DeleteResult (below).

Calling the client directly

For everything else, call AgentMemoryHttpClient (or IAgentMemory) with agent.Execution() and agent.Address():

// outcome with the recall's receipt and the same run
await client.ReportOutcomeAsync(agent.Execution(), agent.Address(), new OutcomeReport
{
    RunId = agent.RunId,
    ReceiptId = context.ReceiptId,
    BuildSuccess = true,
    TestPassRate = 1.0,
});

// what became of the write
OperationReceipt? receipt = await client.GetOperationAsync(agent.Address(), stored.OperationId);

// delete with the full result
DeleteResult result = await client.DeleteMemoryAsync(
    agent.Execution(), agent.Address(), stored.Id, new DeleteOptions(Cascade: false));
// result.Outcome, result.BlockingDependents, result.Cleanup ("complete" | "pending")

ErasureStatus? erasure = await client.GetErasureStatusAsync(agent.Execution(), agent.Address(), stored.Id);

// conditional update
UpdateOutcome updated = await client.UpdateContentAsync(
    agent.Execution(), agent.Address(), stored.Id, "Alex is vegan", expectedVersion: entry?.Version);
MethodEndpoint
RetrieveAsync(ctx, address, agentId, runId, options?)POST /v1/memory/retrieve
AppendAsync(ctx, address, entry, idempotencyKey?)POST /v1/memory/append
GetMemoryAsync(ctx, address, memoryId[, agentId, userId])GET /v1/memory/{id}
UpdateContentAsync(ctx, address, memoryId, content, expectedVersion[, agentId, userId])PUT /v1/memory/{id}
DeleteMemoryAsync(ctx, address, memoryId, options[, agentId, userId])DELETE /v1/memory/{id}
GetErasureStatusAsync(ctx, address, memoryId[, agentId, userId])GET /v1/memory/{id}/erasure
GetOperationAsync(address, operationId)GET /v1/memory/operations/{id}
ExplainMemoryAsync(address, memoryId[, ct, agentId, userId])GET /v1/memory/{id}/explain
ShareAsync(ctx, address, memoryId, visibility[, agentId, userId])POST /v1/memory/{id}/share
ConsolidateAsync(ctx, address, runId, goal?, extractionInstructions?)POST /v1/memory/consolidate
GetBlocksAsync(ctx, address, agentId)GET /v1/blocks/{workspaceId}
UpdateBlockAsync(ctx, address, label, value, agentScope = "*")PUT /v1/blocks/{workspaceId}
CurateBlockAsync(ctx, address, label, action, match?, value?, agentScope = "*")POST /v1/blocks/{workspaceId}/curate
ReportOutcomeAsync(ctx, address, report)POST /v1/outcomes
RecordRetrievalFeedbackAsync(ctx, address, feedback)POST /v1/retrieval-feedback

Errors

Results that are part of a method's contract are returned as values: null for not found, DeleteOutcome / ShareOutcome / UpdateOutcome for refusals and conflicts. Any other non-success status throws HttpRequestException (via EnsureSuccessStatusCode).

On this page