Two independent grounded passes (installed-source introspection + official MS Learn via MCP) produce a per-need adopt/keep decision table for using MAF features well in Fase 2, instead of reinventing them. Headline: Microsoft's Workflows "State Isolation" page documents verbatim the exact footgun Spike B(b) found today — a reused Workflow accumulates agent threads across runs; the fix is a fresh-instance-per-run factory. Our fresh_workflow() IS the official pattern. Key verdicts: ADOPT real UsageDetails token counts + a budget ChatMiddleware + native builder round caps + GA @tool/MCP + observability; KEEP the hand-rolled structural VerdictStore and inline validator (MAF memory/eval are the wrong shape); ROLL a tiny role->deployment map (declarative is preview/not installed). Corrections recorded: CLAUDE.md "Magentic experimental" stands at doc-level (no code gate); Spike D extend_instructions is two-arg (source_id, instructions). Skills answer: method-as-Skill yes (MAF consumes SKILL.md natively, experimental); MAF-docs-mirror Skill no (rots vs live MCP); the digest lives in this map. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fif1r1En5W542HbZV88yMH
15 KiB
MAF capability map — feature-utilization for portfolio-optimiser
Purpose. A living map of what Microsoft Agent Framework (MAF)
agent-framework-core1.9.0 actually offers, what this repo currently hand-rolls, and a per-need decision on whether to adopt the MAF feature or keep our own. It exists so Fase 2 wires MAF into core using its features well instead of reinventing them. Front-end input to the Fase 2/trekbrief.Provenance & trust. Grounded by two independent passes on 2026-06-24: (1) source-introspection of the installed
agent_framework1.9.0 (+agent_framework_orchestrations1.0.0) withfile:linecitations, and (2) official Microsoft Learn docs via themicrosoft-learnMCP. Each verdict is tagged [verified-source] (read in installed bytes), [verified-docs] (official Learn), or [hypothesis] (needs a confirming spike). Version caveat: Learn pages are not version-pinned (the .NET API ref showsv1.0.0-rc2); where source and docs disagree, the installed 1.9.0 source wins for our code. Triggered the search-first + verification duties: every symbol below was read or cited, not recalled.
0. Headline: Microsoft documents the exact footgun Spike B(b) found
Today's /trekreview BLOCKER was that Spike B(b) "confirmed" fan-out state isolation with a
tautological call-counter. The rebuilt experiment showed a reused ConcurrentBuilder Workflow
accumulates the conversation thread across .run() calls (project N contaminates N+1). MAF's
own Workflows "State Isolation" page states this verbatim [verified-docs]:
"Agent threads are persisted across workflow runs… content generated by the agent will be available in subsequent runs of the same workflow instance… it can also lead to unintended state sharing if the same workflow instance is reused for different tasks. To ensure each task has isolated agent state, wrap agent and workflow creation inside a helper method so each call produces new agent instances with their own threads. It is not recommended to reuse a single workflow instance for multiple tasks or requests." — learn.microsoft.com/agent-framework/workflows/state
So our fresh_workflow() factory is the documented Microsoft pattern, and the de-risk
assumption is confirmed both empirically (Spike B) and against official guidance. The lesson is
the thesis of this document: knowing MAF's semantics is load-bearing — a missing piece of that
knowledge produced a false-confirm that a counter-test would have shipped.
Mechanism, traced in installed source [verified-source]: cross-run memory flows exclusively
through AgentSession.state (_sessions.py:772). With a session and no explicit context
provider, Agent.run auto-injects an InMemoryHistoryProvider (_agents.py:1200-1209);
save_messages appends into state["messages"] (_sessions.py:889-890); the next run reloads
the full history via SessionContext.get_messages(include_input=True) (_sessions.py:315-348).
A reused workflow threads one AgentSession into every run → accumulation. A fresh instance per
run gets a clean session.
1. Feature-utilization decision table (the core deliverable)
| Need | We hand-roll now | MAF 1.9.0 feature | Status | Verdict | Fase 2 action |
|---|---|---|---|---|---|
| Token measurement | len(text.split()) proxy (spikes/_harness.py) |
UsageDetails on ChatResponse/AgentResponse (_types.py:393-414), real provider counts |
GA [verified-source] | ADOPT | Read response.usage_details["total_token_count"]; delete word-count. Handle None (not all providers report). |
| Token cap / budget stop | TokenMeter.charge raises BudgetExceeded |
Stateful ChatMiddleware reading context.result.usage_details, short-circuits (_middleware.py:592); officially the "Guardrails & termination" use case |
GA [verified-source+docs] | ADOPT | Implement budget as a ChatMiddleware; share one meter across agents. No native orchestration-level token cap exists. |
| Round / iteration cap | Budget.max_rounds + tick_round() |
GroupChatBuilder.with_max_rounds(n) (_group_chat.py:832); Magentic max_round_count ctor kwarg |
GA (orchestrations 1.0.0) [verified-source] | ADOPT native; drop counter | Use builder round caps for GroupChat/Magentic. Concurrent is single-shot (no rounds). Confirm "1 turn = 1 round" matches intent. |
| Fan-out state isolation | fresh_workflow() factory (Spike B) |
Documented "factory / helper per run" pattern | core [verified-docs] | KEEP — it is the official pattern | Promote fresh_workflow() into a core fan-out factory; never reuse a built workflow across projects. |
| Stop / resume around budget | — | Workflow checkpointing: WorkflowBuilder(checkpoint_storage=…) + run(checkpoint_id=…) (_workflow.py:707-731) |
GA [verified-source] | ADOPT (later) | Optional for resumable budget-stops; superstep-granular (not mid-LLM-call); resume needs identical graph. Defer past MVP unless needed. |
| Cost control on long debates | — | _compaction.py (TokenBudgetComposedStrategy, ContextWindowCompactionStrategy) |
GA [verified-source] | COMPLEMENT | Reduces per-call tokens; does NOT cap spend or stop. Pair with the meter only if debates grow long (D5: not MVP). |
| VerdictStore retrieval (ExpeL) | Structural Jaccard over typed cost-codes + measure-type + magnitude bucket (spikes/d_verdictstore.py) |
MemoryStore/MemoryContextProvider (keyword/substring); mem0 (embeddings) |
experimental / Preview, wrong shape [verified-source+docs] | KEEP ours | MAF retrieval is bag-of-words or embedding; cannot do structural-typed matching. Our store stays. |
| ExpeL injection seam | ExpeLContextProvider(ContextProvider) |
ContextProvider base (_sessions.py:351) — source_id required |
GA [verified-source] | KEEP seam, FIX call | Real contract. Correct the call: extend_instructions(source_id, instructions) — two args, source_id first (_sessions.py:253-262), not the single-list form in the spike. |
| Deterministic blocking validator | Hybrid IR+solver+MC returning ValidatedProposal|Rejection (spikes/c_validator.py) |
_evaluation.py (Evaluator/LocalEvaluator) |
experimental, wrong shape [verified-source] | KEEP ours | MAF eval is offline batch returning a quality score; our validator is an inline gate returning a domain object the system branches on. Not interchangeable. |
| Maker-checker regression scoring | — | LocalEvaluator + @evaluator (_evaluation.py) |
experimental [verified-source] | OPTIONAL (CI only) | "Did the checker catch the planted flaw?" fits an offline CI harness. Not for the live loop. Nice-to-have, not Fase 2 core. |
| Model map (role→deployment) | Planned config | declarative/ (AgentFactory.create_agent_from_yaml) |
Preview, not installed [verified-source+docs] | ROLL OWN | No first-class role→model registry in MAF; declarative is per-agent YAML + prerelease. Build a tiny role→deployment dict/YAML → chat-client ctor. |
| Wrap validator / data fns as tools | Planned | @tool / FunctionTool with schema= (_tools.py) |
GA [verified-source] | ADOPT | GA tool surface with explicit JSON-Schema — fits "JSON-Schema-validated, fail-fast". |
| Data access via MCP | Planned | MCPStdioTool (local) / MCPStreamableHTTPTool (remote+SSE) / MCPWebsocketTool (_mcp.py) |
GA core [verified-source+docs] | ADOPT | MCPStdioTool for local-profile data servers; MCPStreamableHTTPTool for remote. Pass creds per-run via header provider, never persisted. |
| Method as Agent Skill | Planned (CLAUDE.md) | SkillsProvider.from_paths(...) consumes standard SKILL.md folders (_skills.py) |
experimental [verified-source] | ADOPT (pin API) | MAF natively consumes agentskills.io SKILL.md + references/+scripts/. Surface is experimental → pin version, watch for breaks. |
2. Verified MAF semantics worth codifying (the digest)
These are the behaviors whose absence caused today's BLOCKER — capture them so the next session doesn't relearn them the hard way:
- Agents are stateless per run; state lives in the session.
agent.run(...)with no session carries no history; passsession=agent.create_session()to accumulate. Sessions are bound to the agent that created them (only same-provider agents can share). [verified-docs] - Reused workflow ⇒ shared thread ⇒ contamination. Build fresh per task via a factory. The
only cross-run channel is
AgentSession.state. [verified-source+docs] ContextProviderrequiressource_id(positional) for attribution; providers filter each other's contributions by it (_sessions.py:362-368). A history provider instance is shared across sessions — never store per-task state on the provider; store it in the session. [verified-source+docs]- Orchestration round semantics: GroupChat round = one orchestrator selection/dispatch cycle;
with_max_roundsenforced in_handle_response(not the initial handler). If you subclassBaseGroupChatOrchestrator, the builder'smax_rounds/termination_conditionare ignored — you must set them yourself. [verified-source+docs] - Magentic caps are constructor kwargs, not
with_*methods:max_round_count,max_stall_count(default 3),max_reset_count. There is NOwith_max_round_count. [verified-source] - Token usage is on every response as
UsageDetailsand as the OTel metricgen_ai.client.token.usage; there is no cost/dollar metric — derive cost = tokens × your per-model pricing downstream. [verified-source+docs] - Middleware short-circuit = the official termination/guardrail mechanism (return without
calling
next); function-middleware blocking needs aFunctionInvokingChatClient. [verified-docs]
3. Corrections to existing repo claims (verification duty)
- CLAUDE.md "Magentic is experimental (NOT the default)" — stands, with a nuance. The installed
agent_framework_orchestrations1.0.0 carries no@experimentalcode gate [verified-source], but Microsoft's docs explicitly mark agent-orchestration features experimental, Magentic most so ("untested outside the original Magentic-One design") [verified-docs]. So the invariant is correct at the documentation/maturity level; it is just not enforced by a decorator. Keep the invariant; understand it is a doc-level stance, not a code signal. - STATE.md "agent-framework-core 1.9.0" — confirmed [verified-source] (
importlib.metadata). CLAUDE.md was corrected 1.8.0→1.9.0 in Fase 1. - Spike D
extend_instructions([...])(single list) is the wrong signature. Installed GA isextend_instructions(source_id, instructions)— two args,source_idfirst (_sessions.py:253-262) [verified-source]. Spike D's tests pass because they exerciseretrieve()ranking, not the injection path; fix the call when promoting the ExpeL seam to core in Fase 2. - API-naming to confirm in 1.9.0: docs use
AgentSession/ContextProvider; older docs sayAgentThreadand noteBaseContextProvider/BaseHistoryProvideras deprecated aliases. The installed source usesSessionContext/ContextProvider/AgentSession/InMemoryHistoryProvider[verified-source] — trust the source for our code.
4. The Skills question (explicit answer to "should we skillify MAF knowledge?")
No to a MAF-documentation-mirror Skill; yes to two narrow, real uses.
- Why not a docs-mirror suite: MAF docs are live via the
microsoft-learnMCP and the installed source. Mirroring them into local Skills duplicates a moving target (1.9→1.10→…), rots, and violates DRY + the verification duty (a stale local copy can drift from truth). It is the docs-mirror anti-pattern for Skills and gold-plating against D5/D6. - Yes #1 — the method as a Skill (already in CLAUDE.md). Our portfolio-optimization method
(debate → validate → learn) is correctly an Agent Skill, and MAF can natively consume a
standard
SKILL.mdfolder viaSkillsProvider.from_paths(...)[verified-source]. This is a capability, not a reference dump. Caveat: the Skills surface is experimental — pin and watch. - Yes #2 — a digest, which is THIS document. §2 (verified semantics) + §3 (corrections) are
exactly the curated "MAF patterns + footguns" an agent needs while authoring. Their durable home
is this living capability-map (and, when the method-Skill lands, its
references/), not a separate skill suite. A standalone "MAF-expert Skill" only pays off if many future sessions need invokable MAF fluency on demand — defer that decision until Fase 2 shows the need; the raw material (this doc) will already exist.
Net: the capability-map is the no-regret first artifact; it feeds both the Fase 2 design and any later skillification, and it answers "skillify?" with "the method yes, the reference no."
5. Fase 2 implications (what changes in the brief)
- Replace hand-rolled token measurement with
UsageDetails; re-implement the budget cap as a sharedChatMiddleware; use native builder round caps. The hand-rolledBudget/TokenMetershrink to a thin shared-meter object the middleware drives. - Promote
fresh_workflow()to a core fan-out factory — it is the documented isolation pattern. - Keep the hand-rolled VerdictStore (structural retrieval) and validator (inline gate); MAF's
memory and eval are the wrong shape. Promote the
ExpeLContextProviderseam with the two-arg fix. - Adopt GA
@tool+MCPStdioTool/MCPStreamableHTTPToolfor tools/data access. - Roll a tiny role→deployment config map (declarative is preview + not installed).
- Wire observability (
gen_ai.client.token.usage) for cost tracking; derive cost from pricing. - Production-risk flags (D2/D6): mem0/redis memory, Cosmos/Redis history, and declarative are all Preview — do not put them on the MVP critical path; the GA core (sessions, middleware, observability, tools, MCP, checkpointing) is enough for the vertical slice.
6. D7 sibling note
The Claude Agent SDK sibling (eget repo, deler kun spec+golden-suite) faces the same feature-utilization discipline against a different substrate. This map's structure (need → feature → adopt/keep verdict) is the reusable analog; the answers differ. The shared golden-suite must not bake in MAF-specific assumptions (e.g., MAF's session-accumulation semantics).
Sources: installed agent_framework 1.9.0 source (file:line cited inline); Microsoft Learn —
workflows/state, orchestrations/{group-chat,magentic}, agents/{middleware,observability,evaluation,
conversations/storage}, integrations/chat-history-memory-provider, agents/declarative,
agents/tools/local-mcp-tools. Two independent research passes, 2026-06-24.