Skip to main content
Microsoft Agent Framework (MAF) has two distinct product surfaces, and this package covers both. The agent SDK is the one most people start with: client.as_agent(...) with context_providers. The Agent Harness (create_harness_agent) is a separate runtime with its own memory subsystem — a topic notebook, an extraction pass per turn, and a periodic consolidation rewrite — plugged in through two storage interfaces it accepts as constructor arguments.
Requires Python 3.11+ and agent-framework>=1.0. The harness surfaces need agent-framework>=1.13 and are imported lazily, so an older install keeps working for the SDK surfaces and raises a clear error only if you reach for a harness class.

Overview

All of them take an already-constructed MaximemSynapSDK — your app owns the SDK and its credentials.

Setup

The pip package is maximem-synap-microsoft-agent, but the import drops the maximem- prefix and uses underscores: from synap_microsoft_agent import ....
.env
See SDK Initialization for the full lifecycle.

The agent SDK

Basic integration

SynapContextProvider handles both halves of memory on a normal MAF agent — the read before the turn and the write after it:
Reads degrade gracefully on a Synap outage — empty context is injected, the error is logged, and the agent still answers. Writes surface failures so silent data loss is impossible.

Core concepts

Context provider

SynapContextProvider runs on MAF’s before_run and after_run hooks. Before the turn it builds a query from the incoming messages, fetches from Synap, and appends the result to the agent’s instructions. After the turn it records the exchange back to Synap.

History provider

SynapHistoryProvider handles the verbatim transcript, through get_messages and save_messages, which MAF calls for you.
It is orthogonal to SynapContextProvider: one stores semantic memory, the other the literal transcript.

Short-term context

SynapShortTermContextProvider injects a compacted summary of the current conversation, refreshed each turn. Use it when the transcript is too long to replay but its shape still matters.

The Agent Harness

The harness ships its own memory subsystem: a MEMORY.md index of pointer lines, one record per topic, an LLM extraction pass per turn, and a periodic consolidation rewrite. It is genuinely good, and this integration does not replace it. What it replaces is the two things underneath — where memory is stored, and how it is retrieved. Topic selection in the stock harness is lexical: relevance is the size of the word overlap between your question and the topic’s title and summary. A topic filed under “billing” is invisible to a question about “invoices”. That is the gap.

When this is worth it

We benchmarked it rather than asserting it, and the result is narrower than a blanket “upgrade”. We filed a topic under billing — customers charged monthly in arrears, failed charges retried three times — then asked “When do we send invoices to customers, and what happens if one fails?”. The stock file-backed store answered “I don’t know.” The topic was in its own memory directory the whole time; “billing” and “invoices” simply share no words, so it was never loaded. With Synap underneath, the same agent answered both halves correctly. That is the case this integration is for: reach, not economy. On fixtures where the question already shares vocabulary with the topic, the two are equally correct — and the file store is faster. The cost is a retrieval round trip on every turn. Assembling the memory block took roughly 2 seconds with Synap against 5 milliseconds from local files. Prompt size barely moves, because the harness already loads a selection of topics rather than everything. So: use Synap where memory outgrows one vocabulary — long-lived agents, many sessions, memory shared across projects or across agents. On a handful of topics phrased the way you ask about them, MemoryFileStore is the better tool and we would rather say so.

Basic integration

create_harness_agent accepts exactly one history_provider. Both SynapHistoryProvider and the harness memory provider are HistoryProviders, so passing both silently keeps whichever came last and drops the other — no error, no warning. Pick one:
  • history_provider=SynapHistoryProvider(...) — Synap owns the transcript, the harness topic subsystem is off.
  • history_provider=create_synap_harness_memory(...) — the harness owns extraction and consolidation, Synap is the memory beneath it.

Core concepts

What Synap holds, and what stays local

This is the part worth understanding before you deploy it. MemoryStore is a record store. The harness reads a topic record, adds a line, and writes it back — so a record has to come back exactly as it went in. Synap deliberately does not work that way: memories.create runs an extraction pipeline that rewrites, splits, and merges what you submit. In our testing, a topic record submitted as JSON came back as four extracted memories with no JSON envelope and the text rephrased into the third person. The substance survived; the record did not. Reading records back from Synap would therefore feed the harness a rewritten record, which it would rewrite again next turn, and again at the next consolidation. So the split is: The default record store lives for the life of the process. After a restart it is cold, and a topic that is not in it reports as not-found — which the harness handles by starting a fresh record. The topic looks new; no corrupted record ever enters the loop. Meanwhile everything written on previous runs is still in Synap and still reaches the prompt, through the recall block described below. Pass your own record_store to survive restarts:

The recall block

MEMORY.md is assembled fresh on every turn: MAF’s pointer lines, unchanged, plus a Synap recall block underneath.
That block is the reason to use this integration. It carries memory the record layer never had — written on a previous run, by a previous process, or by a different agent against the same scope. It costs one retrieval per turn (about half a second, measured against production). Turn it off with include_recall=False if you would rather not pay that. The harness exposes a search_memory_transcripts tool. On the file store that is a substring match over saved turn files, so a question worded differently from the transcript finds nothing. Here it becomes a Synap retrieval call, so wording does not have to match.

File memory

SynapAgentFileStore backs the seven file_memory_* tools the harness gives the model:
file_memory_grep searches both ways: a regex over files written this session, and a meaning-based lookup against Synap attributed to MEMORY.md. The regex half is exact and is what the model expects from a tool named grep; the semantic half finds memories no regex could match. file_memory_delete is real — a file written through this store resolves to the memories it produced and deletes them.
file_memory_ls only lists files this process wrote. There is no list-memories-by-scope API in Synap today, so after a restart the agent can still read its files by name and grep them by meaning, but it cannot browse them. Within a session — which is what file memory is scoped to — this is invisible.

Complete example: both harness surfaces

Every harness API is marked experimental upstream and lives behind private module paths that Microsoft says may move. Pin a tested agent-framework version and re-run your tests on each minor release. This package never suppresses the ExperimentalWarning — if MAF wants you to know the surface is unstable, hiding that would not be doing you a favour.

Advanced patterns

Scoping

Both harness stores take user_id and customer_id; at least one is required. customer_id is required on B2B instances. See Memory Scopes. For multi-tenant hosts, SynapMemoryStore also takes a scope_resolver callable over the session, which partitions records per tenant. It does not re-scope the Synap calls themselves — build one store per tenant for that. FileMemoryProvider(scope=...) decides which folder file memory lands in: None isolates per session, an explicit value groups across sessions. Line it up with the store’s scope, or the tool surface and the memory will disagree about whose files they are.

A new scope needs a corpus before recall is useful

Recall is only as good as what is in the scope, and a nearly-empty scope returns nothing at all rather than a little. In testing, a scope holding three memories returned an empty context on every setting, while the same scope at a dozen returned content consistently. That matters because of how it looks from the outside: the recall block is absent and the integration appears broken. It isn’t — there is genuinely nothing to return yet. Let the agent accumulate a corpus over several sessions before judging recall quality.

Failure semantics

Read failures must not break a user-facing turn. Silent write failures would corrupt the memory pool, so they raise.

Going further

Next steps

Semantic Kernel

Kernel plugin for Microsoft Semantic Kernel.

deepagents

The same harness pattern for LangChain’s deepagents.

Context Fetch

The retrieval API behind every surface here: modes, scopes, and response shapes.

Memory Scopes

How user_id, customer_id, and conversation_id interact across reads.