Skip to content

Add OCG-backed long-term agent memory (ports agentspan#298)#156

Open
NicholasDCole wants to merge 1 commit into
mainfrom
feature/add_memory
Open

Add OCG-backed long-term agent memory (ports agentspan#298)#156
NicholasDCole wants to merge 1 commit into
mainfrom
feature/add_memory

Conversation

@NicholasDCole

Copy link
Copy Markdown

Ports the SDK side of the "OCG-backed agent memory" feature from agentspan-ai/agentspan#298 to the C# SDK. The server-side compiler that consumes the wire config lives in conductor-oss/conductor (agentspan-server: LongTermMemoryConfig.java, AgentConfig.longTermMemory/feedbackSink).

What was ported

  • OCGMemoryStore (Conductor.AI/OCGMemory.cs) — a synchronous MemoryStore HTTP adapter over the OCG BFF:
    • AddPOST /api/v1/memories
    • SearchPOST /api/v1/memories/search (folds the human good/bad signal into result content)
    • DeleteDELETE /api/v1/memories/{key}, ListAllGET /api/v1/memories, Clear (fan-out)
    • GetFeedbackLinksPOST /api/v1/memories/{key}/feedback-links (mints signed good/bad capability URLs)
    • Auth via Authorization: Bearer <token>; the token is held client-side. Also exposes MemorySummary, FeedbackEvent, FeedbackLinks, and OCGMemory.BuildMemorySummarizer.
  • Agent params (Conductor.AI/Agent.cs, + AgentBuilder fluent methods): SemanticMemory, MemorySummaryModel, FeedbackSink (Action<FeedbackEvent>).
  • Serializer emission (Conductor.AI/AgentConfigSerializer.cs) — the most important piece. When an agent's SemanticMemory is backed by an OCGMemoryStore, emits:
    • longTermMemory: {ocgUrl, credential, agent, user, scope, maxResults, summaryModel}credential is a server-resolvable secret NAME (OCG_PUBLIC_KEY), never the raw client token; summaryModel falls back to the agent's own model.
    • feedbackSink: {taskName: "{name}_feedback_sink"} when a feedback sink is set.
  • Feedback-sink worker (Conductor.AI/WorkerManager.cs) — registers a {name}_feedback_sink worker (analogous to the existing callback/gate workers) so the compiled server path can hand the FeedbackEvent (distilled summary + signed good/bad URLs) to the user's FeedbackSink for out-of-band delivery. Best-effort. The capability URLs are never surfaced to the agent's LLM.
  • Tests (Conductor.AI.Tests/OcgMemoryTests.cs, xUnit) — store adapter (add posts value not string_value/confidence; search folds good/bad signal; feedback-link route; non-2xx raises; blank url/agent rejected) and serializer emission (present / absent / summaryModel fallback / non-OCG store no-op).
  • Example Conductor.AI.Examples/27_OcgMemory mirroring Python examples/118_ocg_memory.py.
  • CHANGELOG entry.

What was skipped

Client-side pre/post-run memory hooks from Python runtime.py (_apply_memory_retrieval, _maybe_save_conversation_memory, _parse_summary_output) are not ported. This SDK has no client-side agent run loop — AgentRuntime.RunAsync is control-plane only (it serializes the agent, starts the workflow, and polls the server, which runs the LLM loop). Memory therefore activates entirely through the serialized longTermMemory config that the server-side compiler expands into pre-loop retrieval + post-loop distill/save/feedback. The corresponding runtime-hook unit tests were skipped for the same reason.

C#-specific adaptations

  • OCGMemoryStore accepts an injectable HttpClient for testing (the C# analogue of Python's httpx.Client + MockTransport); the async HttpClient is driven synchronously to satisfy the sync MemoryStore interface.
  • OCG-backed detection uses a type check (Store is OCGMemoryStore) rather than Python's duck-typed _base attribute probe.
  • MemoryEntry is immutable (init-only), so Add returns the key without mutating the entry.
  • FeedbackLinks is a typed record; MemorySummary/FeedbackEvent are plain classes.

Test results

dotnet test Conductor.AI.Tests65 passed, 0 failed, 0 skipped (9 new + 56 pre-existing). The main library and the new example both build clean.

🤖 Generated with Claude Code

Port the "OCG-backed agent memory" feature from agentspan-ai/agentspan
PR #298 to the C# SDK.

- OCGMemoryStore: synchronous MemoryStore HTTP adapter over the OCG BFF
  (search / save / delete / list + feedback-link minting), with the OCG
  bearer token held client-side.
- Agent params: SemanticMemory (handle), MemorySummaryModel, FeedbackSink.
- Serializer: emit longTermMemory {ocgUrl, credential, agent, user, scope,
  maxResults, summaryModel} + feedbackSink {taskName} when an agent's
  SemanticMemory is OCG-backed. credential is a server-resolvable secret
  NAME (OCG_PUBLIC_KEY), never the raw client token.
- WorkerManager: register a {name}_feedback_sink worker so the compiled
  server path can hand human good/bad capability links to FeedbackSink
  out-of-band (never shown to the agent's LLM).
- Tests (xUnit) for the store adapter and serializer emission.
- Example 27_OcgMemory mirroring Python examples/118_ocg_memory.py.

Client-side pre/post-run memory hooks from Python runtime.py are not ported:
this SDK's run path is control-plane only (the server runs the agent loop),
so memory activates entirely through the serialized longTermMemory config.
The server-side compiler lives in conductor-oss/conductor (agentspan-server).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Base automatically changed from feature/combine-agentspan to main July 18, 2026 01:19
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.

2 participants