[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M69AV8BVRD34GACS40NB3NS0":3,"skill-same:01M69AV8BVRD34GACS40NB3NS0":95},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":30,"linkOnly":31,"markdownContent":32,"markdownBody":33,"frontMatter":34,"refName":38,"commitSha":39,"skillDirectory":40,"forksCount":41,"repositoryHtmlUrl":42,"repositoryHomepageUrl":6,"ownerHtmlUrl":43,"ownerBlogUrl":44,"ownerDescription":45,"updatedAt":46,"install":47,"resources":60},"01M69AV8BVRD34GACS40NB3NS0","agent-framework-workflows-py",null,"Build deterministic multi-step, multi-agent workflows in Python with the Microsoft Agent Framework (`agent-framework`). Use when composing `Executor` nodes and edges, embedding `Agent` instances as workflow nodes, streaming workflow events, fan-out\u002Ffan-in, conditional routing, loops, checkpointing and resume, human-in-the-loop with `ctx.request_info()`, sub-workflows with `WorkflowExecutor`, wrapping a workflow as an agent with `workflow.as_agent()`, or using the high-level orchestration builders (`SequentialBuilder`, `ConcurrentBuilder`, `HandoffBuilder`, `GroupChatBuilder`, `MagenticBuilder`).","microsoft\u002FLearn-Microsoft-Agent-Framework-with-Foundry-ZavaShop-Supply-Chain-Workshop","microsoft","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F6154722?v=4","01M698VYDXZ6JW5WH3CRV650R8","Microsoft","01M69ATPGBJQAGJHX0G1M5B2CN",48,"MIT","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002FLearn-Microsoft-Agent-Framework-with-Foundry-ZavaShop-Supply-Chain-Workshop\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fagent-framework-workflows-py\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[24,25,26,27,28,29,9],"agent framework","workflows","agent framework workflows py","python","orchestration","workflow","2026-11-10 10:12:30",false,"---\nname: agent-framework-workflows-py\ndescription: Build deterministic multi-step, multi-agent workflows in Python with the Microsoft Agent Framework (`agent-framework`). Use when composing `Executor` nodes and edges, embedding `Agent` instances as workflow nodes, streaming workflow events, fan-out\u002Ffan-in, conditional routing, loops, checkpointing and resume, human-in-the-loop with `ctx.request_info()`, sub-workflows with `WorkflowExecutor`, wrapping a workflow as an agent with `workflow.as_agent()`, or using the high-level orchestration builders (`SequentialBuilder`, `ConcurrentBuilder`, `HandoffBuilder`, `GroupChatBuilder`, `MagenticBuilder`).\nlicense: MIT\nmetadata:\n  author: Microsoft\n  version: \"1.0.0\"\n  package: agent-framework\n---\n\n# Microsoft Agent Framework — Workflows (Python)\n\nBuild deterministic, durable, observable multi-agent \u002F multi-step pipelines on top of the Microsoft Agent Framework Python SDK. Workflows are a graph of `Executor` nodes connected by typed edges; agents are first-class executors; events stream as the graph runs; state can be checkpointed and replayed.\n\n## Architecture\n\n```\n        ┌──────────────────────────────────────────────────────────────┐\n        │                       WorkflowBuilder                        │\n        │  start_executor=A, output_from=[...], intermediate_output…   │\n        └─────────────┬────────────────────────────────────────────────┘\n                      │ .build()\n                      ▼\n               ┌────────────┐\n               │  Workflow  │   ──►  .run(input)  \u002F  .run(stream=True)\n               └─────┬──────┘                       \u002F  .run(checkpoint_id=…)\n                     │                              \u002F  .run(responses={…})\n                     ▼\n        ┌────────────────────────────┐\n        │  Executor graph (DAG)      │\n        │   • Executor \u002F @executor   │\n        │   • Agent \u002F AgentExecutor  │   events: executor_invoked,\n        │   • WorkflowExecutor (sub) │           executor_completed,\n        │   • Orchestration builders │           output, intermediate,\n        └────────┬───────────────────┘           request_info, status,\n                 │                                superstep_completed\n                 ▼\n        ┌────────────────────────────┐\n        │  WorkflowContext (per run) │   ctx.send_message \u002F ctx.yield_output\n        │  state, requests, events   │   ctx.request_info \u002F ctx.set_state\n        └────────────────────────────┘\n```\n\nTwo equivalent authoring styles:\n\n| Style | When to use | Entry points |\n| --- | --- | --- |\n| **Graph (declarative)** | Branching, loops, fan-out\u002Ffan-in, orchestration patterns, sub-workflows, checkpointing | `Executor`, `@executor`, `@handler`, `WorkflowBuilder` |\n| **Functional** | Linear or simply branched pipelines expressed as plain async Python | `@workflow`, `@step`, `ctx.request_info` |\n\n## Installation\n\n```bash\n# Core workflows ship inside agent-framework \u002F agent-framework-core\npip install agent-framework --pre\n\n# Optional: graph visualization (WorkflowViz.export → SVG)\npip install \"agent-framework[viz]\" --pre\n# Also install GraphViz binary: https:\u002F\u002Fgraphviz.org\u002Fdownload\u002F\n\n# Optional: orchestration builders ship inside the core package; or pin directly\npip install agent-framework-orchestrations --pre\n```\n\nOrchestration builders are imported from the `agent_framework.orchestrations` submodule:\n\n```python\nfrom agent_framework.orchestrations import (\n    SequentialBuilder,\n    ConcurrentBuilder,\n    HandoffBuilder,\n    GroupChatBuilder,\n    MagenticBuilder,\n)\n```\n\n## Environment Variables\n\nWorkflow samples that embed an `Agent` use `FoundryChatClient` (lightweight, project-backed; no server-side agent lifecycle):\n\n| Variable | Purpose |\n| --- | --- |\n| `FOUNDRY_PROJECT_ENDPOINT` | Azure AI Foundry Agent Service (V2) project endpoint |\n| `FOUNDRY_MODEL` | Model deployment name in your Foundry project |\n\nAuthenticate via `azure.identity.AzureCliCredential()` (run `az login` first), or substitute any `SupportsChatGetResponse` chat client (OpenAI, Azure OpenAI, etc.).\n\n## Core Workflow\n\n### 1. Executors and edges\n\nAn `Executor` is a node. Inputs and outputs are typed. Handlers receive a `WorkflowContext[T_Out, T_W_Out]`:\n\n- `ctx.send_message(value)` — forward to downstream nodes\n- `ctx.yield_output(value)` — produce a workflow output (terminal yield, but workflows complete when **idle**, not on first yield)\n- `ctx.set_state(key, value)` \u002F `ctx.get_state(key)` — per-run scratchpad\n- `ctx.request_info(...)` — pause for human \u002F external input\n\nTwo equivalent ways to declare a node:\n\n```python\nfrom agent_framework import Executor, WorkflowBuilder, WorkflowContext, executor, handler\nfrom typing_extensions import Never\n\n\nclass UpperCase(Executor):\n    @handler\n    async def to_upper(self, text: str, ctx: WorkflowContext[str]) -> None:\n        await ctx.send_message(text.upper())\n\n\n@executor(id=\"reverse_text\")\nasync def reverse_text(text: str, ctx: WorkflowContext[Never, str]) -> None:\n    await ctx.yield_output(text[::-1])\n\n\ndef create_workflow():\n    upper = UpperCase(id=\"upper\")\n    return (\n        WorkflowBuilder(start_executor=upper)\n        .add_edge(upper, reverse_text)\n        .build()\n    )\n```\n\nRun it:\n\n```python\nworkflow = create_workflow()\nevents = await workflow.run(\"hello world\")\nprint(events.get_outputs())           # ['DLROW OLLEH']\nprint(events.get_final_state())       # WorkflowRunState.IDLE\n```\n\n> **State isolation**: wrap construction in a helper (`create_workflow()`) and call it per run so executor instance state never leaks between runs.\n\n### 2. Agents as workflow nodes\n\n`Agent` objects (and `AgentExecutor`-wrapped agents) plug directly into the graph. Inputs are normally `AgentExecutorRequest`; outputs are `AgentExecutorResponse`. When the start node is an `Agent`, the framework converts `run(str)` \u002F `run(Message(...))` to a request automatically.\n\n```python\nfrom agent_framework import Agent, WorkflowBuilder\nfrom agent_framework.foundry import FoundryChatClient\nfrom azure.identity import AzureCliCredential\n\nclient = FoundryChatClient(\n    project_endpoint=os.environ[\"FOUNDRY_PROJECT_ENDPOINT\"],\n    model=os.environ[\"FOUNDRY_MODEL\"],\n    credential=AzureCliCredential(),\n)\nwriter = Agent(client=client, instructions=\"You write copy.\", name=\"writer\")\nreviewer = Agent(client=client, instructions=\"You review copy.\", name=\"reviewer\")\n\nworkflow = WorkflowBuilder(start_executor=writer).add_edge(writer, reviewer).build()\nevents = await workflow.run(\"Slogan for an affordable electric SUV.\")\n```\n\n### 3. Streaming events\n\n`workflow.run(..., stream=True)` returns an async iterator of `WorkflowEvent`:\n\n| `event.type` | Meaning |\n| --- | --- |\n| `executor_invoked` \u002F `executor_completed` | Lifecycle per node — observable without code changes |\n| `output` | A `ctx.yield_output(...)` value (or agent streaming token via `AgentResponseUpdate`) |\n| `intermediate` | A yield from an executor selected by `intermediate_output_from` |\n| `request_info` | A `ctx.request_info(...)` call — workflow is paused |\n| `status` | `WorkflowRunState.IDLE`, `IDLE_WITH_PENDING_REQUESTS`, etc. |\n| `superstep_completed` | A graph \"super-step\" finished (safe checkpoint boundary) |\n\n```python\nasync for event in workflow.run(initial, stream=True):\n    if event.type == \"output\":\n        print(\"OUT:\", event.data)\n    elif event.type == \"intermediate\":\n        print(\"…  \", event.executor_id, event.data)\n```\n\n## Output selection (`output_from` \u002F `intermediate_output_from`)\n\n`WorkflowBuilder` exposes which executor yields are surfaced. `output_from` is an **allow-list** for Workflow Output, not a routing rule; unselected yields are hidden unless `intermediate_output_from` selects them.\n\n| Selection | Workflow Output (`type='output'`) | Intermediate (`type='intermediate'`) | Hidden |\n| --- | --- | --- | --- |\n| Omit both | All `yield_output` (deprecation warning) | — | — |\n| `output_from=\"all\"` | Every output-capable executor | — | — |\n| `output_from=[answerer]` | Only `answerer` | — | All others |\n| `output_from=[answerer], intermediate_output_from=\"all_other\"` | `answerer` | Every other output-capable executor | — |\n| `intermediate_output_from=\"all_other\"` | Builder-internal default executors only | Every output-capable executor | Builder plumbing |\n| `output_from=[w], intermediate_output_from=[r, rv]` | `w` | `r`, `rv` | Any others |\n\nInvalid: `output_from=\"all_other\"`, `intermediate_output_from=\"all\"`, the same executor in both lists, duplicates, unknown executors, or both lists empty. Deprecated alias: `output_executors` (use `output_from`).\n\nWhen the workflow is exposed via `workflow.as_agent()`, Workflow Output becomes the `AgentResponse.text`; Intermediate Output becomes `text_reasoning` content on the response messages.\n\n## Functional API (`@workflow`, `@step`)\n\nWrite workflows as plain async Python — no graph, no edges. Use normal `if`\u002F`else`, loops, and `asyncio.gather` for branching and parallelism.\n\n```python\nfrom agent_framework import workflow, step\n\nasync def fetch(url: str) -> dict: ...\nasync def transform(d: dict) -> str: ...\n\n@workflow\nasync def pipeline(url: str) -> str:\n    raw = await fetch(url)\n    summary = await transform(raw)\n    return f\"[OK] {summary}\"\n\nresult = await pipeline.run(\"https:\u002F\u002Fexample.com\")\nprint(result.get_outputs()[0])\n\n# Streaming events\nstream = pipeline.run(\"https:\u002F\u002Fexample.com\", stream=True)\nasync for event in stream:\n    if event.type == \"output\":\n        print(event.data)\nfinal = await stream.get_final_response()\n```\n\nAdd `@step` to a function to cache its result across HITL resume or checkpoint restore — without `@step` every call re-executes from the top on resume. `@step` composes with `asyncio.gather` for cached parallel branches. To enable checkpointing, pass `checkpoint_storage` to `@workflow`:\n\n```python\nfrom agent_framework import InMemoryCheckpointStorage, step, workflow\n\nstorage = InMemoryCheckpointStorage()\n\n@step\nasync def fetch_data(url: str) -> dict: ...\n\n@workflow(checkpoint_storage=storage)\nasync def pipeline(url: str) -> str:\n    raw = await fetch_data(url)\n    ...\n```\n\nFunctional workflows also support `as_agent()`, HITL via `ctx: RunContext` + `ctx.request_info()`, and structured `RunContext` access — all opt-in.\n\n## Methods (graph API quick reference)\n\n| Builder method | Purpose |\n| --- | --- |\n| `WorkflowBuilder(start_executor=, checkpoint_storage=, max_iterations=, output_from=, intermediate_output_from=)` | Create a builder |\n| `.add_edge(src, dst, condition=None, target_id=None)` | Connect two executors; optional predicate over the upstream message |\n| `.add_chain([a, b, c])` | Sugar for sequential edges |\n| `.add_fan_out_edges(src, [t1, t2, ...])` | Same message to many targets in parallel; requires at least two targets |\n| `.add_fan_in_edges([s1, s2, ...], dst)` | Collect a `list[T]` from many sources into one handler; requires at least two sources |\n| `.add_switch_case_edge_group(src, [Case(condition=, target=), Default(target=)])` | One-of-N routing |\n| `.add_multi_selection_edge_group(src, targets, selection_func=)` | Dynamic subset fan-out |\n| `.build()` → `Workflow` | Materialize the graph |\n\n| Workflow method | Purpose |\n| --- | --- |\n| `await workflow.run(message, stream=False)` | Run; returns `WorkflowRunResult` (or async iterator if `stream=True`) |\n| `workflow.run(checkpoint_id=...)` | Resume from a saved checkpoint |\n| `workflow.run(responses={request_id: value})` | Resume after `request_info` |\n| `result.get_outputs()` | List of `type='output'` payloads |\n| `result.get_final_state()` | `WorkflowRunState` enum |\n| `result.get_request_info_events()` | Pending HITL requests |\n| `workflow.as_agent(\"agent_id\")` | Wrap workflow as an `Agent` |\n\n| Context method | Purpose |\n| --- | --- |\n| `ctx.send_message(msg, target_id=None)` | Forward to downstream (optionally one specific target) |\n| `ctx.yield_output(value)` | Emit a workflow output (subject to `output_from` selection) |\n| `ctx.set_state(key, value)` \u002F `ctx.get_state(key)` | Per-run state |\n| `await ctx.request_info(request_data=..., response_type=..., request_id=None)` | Pause for human \u002F external input |\n| `@response_handler` | Method that receives the resumed response |\n\n## Complete Example — Spam triage with conditional routing\n\n```python\nimport asyncio\nimport os\nfrom typing import Any\nfrom pydantic import BaseModel\nfrom typing_extensions import Never\n\nfrom agent_framework import (\n    Agent, AgentExecutor, AgentExecutorRequest, AgentExecutorResponse,\n    Message, WorkflowBuilder, WorkflowContext, executor,\n)\nfrom agent_framework.foundry import FoundryChatClient\nfrom azure.identity import AzureCliCredential\n\n\nclass Detection(BaseModel):\n    is_spam: bool\n    reason: str\n    email_content: str\n\n\nclass EmailReply(BaseModel):\n    response: str\n\n\ndef is_spam(expected: bool):\n    def cond(msg: Any) -> bool:\n        if not isinstance(msg, AgentExecutorResponse):\n            return True\n        try:\n            return Detection.model_validate_json(msg.agent_response.text).is_spam == expected\n        except Exception:\n            return False\n    return cond\n\n\n@executor(id=\"to_reply_request\")\nasync def to_reply_request(resp: AgentExecutorResponse, ctx: WorkflowContext[AgentExecutorRequest]) -> None:\n    parsed = Detection.model_validate_json(resp.agent_response.text)\n    await ctx.send_message(AgentExecutorRequest(\n        messages=[Message(\"user\", contents=[parsed.email_content])],\n        should_respond=True,\n    ))\n\n\n@executor(id=\"send_reply\")\nasync def send_reply(resp: AgentExecutorResponse, ctx: WorkflowContext[Never, str]) -> None:\n    parsed = EmailReply.model_validate_json(resp.agent_response.text)\n    await ctx.yield_output(f\"Sent:\\n{parsed.response}\")\n\n\n@executor(id=\"handle_spam\")\nasync def handle_spam(resp: AgentExecutorResponse, ctx: WorkflowContext[Never, str]) -> None:\n    parsed = Detection.model_validate_json(resp.agent_response.text)\n    await ctx.yield_output(f\"Spam: {parsed.reason}\")\n\n\ndef make_agent(name: str, instructions: str, fmt: type[BaseModel]) -> Agent:\n    return Agent(\n        client=FoundryChatClient(\n            project_endpoint=os.environ[\"FOUNDRY_PROJECT_ENDPOINT\"],\n            model=os.environ[\"FOUNDRY_MODEL\"],\n            credential=AzureCliCredential(),\n        ),\n        instructions=instructions,\n        name=name,\n        default_options={\"response_format\": fmt},\n    )\n\n\nasync def main() -> None:\n    detector = AgentExecutor(make_agent(\n        \"spam_detector\",\n        \"Detect spam. Reply JSON {is_spam, reason, email_content}.\",\n        Detection,\n    ))\n    writer = AgentExecutor(make_agent(\n        \"email_writer\",\n        \"Draft a professional reply. Reply JSON {response}.\",\n        EmailReply,\n    ))\n\n    workflow = (\n        WorkflowBuilder(start_executor=detector)\n        .add_edge(detector, to_reply_request, condition=is_spam(False))\n        .add_edge(to_reply_request, writer)\n        .add_edge(writer, send_reply)\n        .add_edge(detector, handle_spam, condition=is_spam(True))\n        .build()\n    )\n\n    request = AgentExecutorRequest(\n        messages=[Message(\"user\", contents=[\"Hi team, follow-up notes from today’s meeting…\"])],\n        should_respond=True,\n    )\n    result = await workflow.run(request)\n    print(result.get_outputs()[0])\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n```\n\n## Orchestration patterns (quick reference)\n\n| Pattern | Builder | What it does |\n| --- | --- | --- |\n| Sequential | `SequentialBuilder(participants=[...])` | Chain agents with a shared conversation context |\n| Concurrent | `ConcurrentBuilder(participants=[...])` | Fan-out to all participants in parallel, default aggregator returns combined `Messages` |\n| Handoff | `HandoffBuilder(participants=[...]).with_start_agent(...)` | Mesh routing; auto-injected handoff tools; `HandoffAgentUserRequest` for HITL |\n| GroupChat | `GroupChatBuilder(participants=[...]).with_orchestrator(agent=)` | Manager (agent or function) picks the next speaker each round |\n| Magentic | `MagenticBuilder(participants=[...], manager_agent=, max_round_count=)` | Multi-agent planner\u002Fexecutor with progress ledger, plan review, checkpointing |\n\nEach builder exposes `.build()` → `Workflow`, plus `.as_agent()` to expose the whole orchestration as a single agent. All builders honor the same `output_from` \u002F `intermediate_output_from` selection model.\n\n`HandoffBuilder` requires every participant agent to set `require_per_service_call_history_persistence=True`, or `.build()` raises `ValueError`.\n\n## Human-in-the-loop (`ctx.request_info`)\n\n```python\nfrom agent_framework import Executor, WorkflowContext, handler, response_handler\nfrom dataclasses import dataclass\n\n@dataclass\nclass HumanFeedback:\n    prompt: str\n\nclass TurnManager(Executor):\n    @handler\n    async def ask(self, _: str, ctx: WorkflowContext) -> None:\n        await ctx.request_info(\n            request_data=HumanFeedback(prompt=\"Approve? (yes\u002Fno)\"),\n            response_type=str,\n        )\n\n    @response_handler\n    async def on_feedback(self, original: HumanFeedback, reply: str, ctx: WorkflowContext[Never, str]) -> None:\n        await ctx.yield_output(f\"User said: {reply}\")\n```\n\n```python\n# Drive the loop:\nstream = workflow.run(\"start\", stream=True)\npending = await collect_request_info(stream)\nwhile pending:\n    answers = {req_id: input(req.prompt) for req_id, req in pending.items()}\n    stream = workflow.run(stream=True, responses=answers)\n    pending = await collect_request_info(stream)\n```\n\nFor agent-level tool approvals, set `approval_mode=\"always_require\"` on a `@tool`; the workflow then emits `request_info` events whose `data` is a `FunctionApprovalRequestContent` — reply with `data.to_function_approval_response(approved=True)`.\n\n## Checkpointing\n\nPlug a checkpoint storage into `WorkflowBuilder(checkpoint_storage=...)` (graph) or `@workflow(checkpoint_storage=...)` (functional). A checkpoint is written after each super-step.\n\n| Storage | Use |\n| --- | --- |\n| `InMemoryCheckpointStorage()` | Tests, demos |\n| `FileCheckpointStorage(storage_path=...)` | Local dev, multi-process recovery |\n| `CosmosCheckpointStorage(...)` | Durable production checkpoints in Azure Cosmos DB NoSQL |\n\n```python\nstorage = InMemoryCheckpointStorage()\nworkflow = (\n    WorkflowBuilder(start_executor=start, checkpoint_storage=storage)\n    .add_edge(start, worker).add_edge(worker, worker)  # self-loop\n    .build()\n)\n\n# Resume from a checkpoint\nlatest = await storage.get_latest(workflow_name=workflow.name)\nevents = workflow.run(checkpoint_id=latest.checkpoint_id, stream=True)\n```\n\nPer-executor state survives a restart by overriding `on_checkpoint_save` \u002F `on_checkpoint_restore`:\n\n```python\nfrom typing import Any\nfrom typing_extensions import override\n\nclass Worker(Executor):\n    def __init__(self, id: str) -> None:\n        super().__init__(id=id)\n        self._cache: dict[int, list[tuple[int, int]]] = {}\n\n    @override\n    async def on_checkpoint_save(self) -> dict[str, Any]:\n        return {\"cache\": self._cache}\n\n    @override\n    async def on_checkpoint_restore(self, state: dict[str, Any]) -> None:\n        self._cache = state.get(\"cache\", {})\n```\n\n## Sub-workflows and `workflow.as_agent()`\n\nEmbed a built `Workflow` as a node via `WorkflowExecutor(child, id=\"child\")`:\n\n```python\nfrom agent_framework import WorkflowExecutor\n\nchild = build_validation_workflow()\nparent = (\n    WorkflowBuilder(start_executor=orchestrator)\n    .add_edge(orchestrator, WorkflowExecutor(child, id=\"validate\"))\n    .add_edge(\"validate\", delivery)\n    .build()\n)\n```\n\nA child's `ctx.request_info(...)` bubbles up as a `SubWorkflowRequestMessage` to the parent — handle it in the parent and reply with `request.create_response(value)` to intercept (no propagation to the caller).\n\nExpose any workflow as an agent:\n\n```python\nagent = workflow.as_agent(\"my_agent\")\nresponse = await agent.run(\"user prompt\")\nprint(response.text)                   # Workflow Output\n# Intermediate Output is preserved as text_reasoning content on response.messages\n```\n\nThis unlocks the reflection pattern (Worker ↔ Reviewer loop wrapped as a single agent), and lets a workflow act as a sub-agent inside another workflow.\n\n## Conventions\n\n- **One executor = one responsibility.** Keep handlers small; transform → forward.\n- **Carry typed payloads on edges**, not raw strings. Use `@dataclass` \u002F Pydantic for structured handoffs.\n- **Use `response_format`** on agents that drive routing — never parse free text.\n- **Store large payloads in `ctx.set_state`** and pass small references (IDs) on edges.\n- **Helper-build pattern**: wrap construction in `def create_workflow(): ...` so each run gets fresh executor instances.\n- **Single-target map pattern**: if one executor emits many messages to one downstream executor, use `.add_edge(src, dst)` rather than `.add_fan_out_edges(src, [dst])`. If those messages must become one batch before the next stage, use a stateful `Executor` with `@handler` to buffer and forward once complete.\n- **Pick explicit `output_from`** in new code; compatibility mode emits a deprecation warning.\n- **`@step` only what's expensive.** Cheap functions can stay un-decorated and replay freely.\n- **Treat participant names as stable identifiers** — especially for Magentic checkpoints.\n\n## Best Practices\n\n- Build the graph once, call `workflow.run(...)` many times; runs share workflow but not executor state when you use the helper-build pattern.\n- Use `stream=True` for any long-running or interactive workflow so you can render progress and capture `request_info`.\n- Combine `@workflow(checkpoint_storage=...)` + `@step` to get durable, replayable pipelines with minimal boilerplate.\n- For HITL: emit a typed `request_data` dataclass so the caller has a contract, not a string blob.\n- For sub-workflows that emit `request_info`, decide up front whether the parent intercepts (handle `SubWorkflowRequestMessage`) or the outer caller receives the request.\n- For orchestration builders, pick `output_from` deliberately — Sequential keeps the last participant by default; Concurrent \u002F GroupChat \u002F Magentic keep their aggregator\u002Fmanager outputs.\n- For visualization, build with `[viz]` extra and call `WorkflowViz(workflow).to_mermaid()` \u002F `.to_digraph()` \u002F `.export(format=\"svg\")`.\n- Validate agent JSON with Pydantic — defensive parsing prevents dead routes.\n\n## Reference Files\n\n| File | Topic |\n| --- | --- |\n| [references\u002Fgetting_started.md](references\u002Fgetting_started.md) | Executors, edges, agents in a workflow, streaming |\n| [references\u002Fcontrol_flow.md](references\u002Fcontrol_flow.md) | Conditions, switch-case, multi-selection, loops, intermediate vs terminal outputs |\n| [references\u002Fparallelism.md](references\u002Fparallelism.md) | Fan-out\u002Ffan-in, mixed-type aggregation, map-reduce, visualization |\n| [references\u002Ffunctional_api.md](references\u002Ffunctional_api.md) | `@workflow`, `@step`, streaming, HITL, agents in functional pipelines |\n| [references\u002Fhuman_in_the_loop.md](references\u002Fhuman_in_the_loop.md) | `ctx.request_info`, approvals, declaration-only tools |\n| [references\u002Fcheckpointing.md](references\u002Fcheckpointing.md) | In-memory \u002F file \u002F Cosmos storage, resume, sub-workflow + HITL checkpoints |\n| [references\u002Forchestrations.md](references\u002Forchestrations.md) | `SequentialBuilder`, `ConcurrentBuilder`, `HandoffBuilder`, `GroupChatBuilder`, `MagenticBuilder` |\n| [references\u002Fcomposition.md](references\u002Fcomposition.md) | `WorkflowExecutor`, sub-workflow request interception, `workflow.as_agent()`, shared sessions, kwargs |\n\n## Workshop-verified gotchas (LAB 04)\n\nThese are the seven traps observed while building the ZavaShop fulfillment workflow in [`workshop\u002FLAB04-fulfillment-workflow\u002Ffulfillment_workflow.py`](..\u002F..\u002F..\u002Fworkshop\u002FLAB04-fulfillment-workflow\u002Ffulfillment_workflow.py). Every bullet has been reproduced against `agent-framework 1.0.0rc3`.\n\n1. **No `from __future__ import annotations`** in modules that use `@response_handler`. The validator (`_request_info_mixin.py`) inspects the **raw** `WorkflowContext[X, Y]` annotation; with deferred annotations it sees a `str` instead of the resolved generic and raises `Invalid handler signature`. Keep workflow modules annotation-eager.\n2. **`workflow.as_agent()` always dispatches `list[Message]`** to the start executor — it asserts `is_type_compatible(list[Message], start.input_types)`. If your true intake input is `str`, write a class-based `IntakeExecutor(Executor)` with **two** `@handler` methods (`str` *and* `list[Message]`) that share a private `_emit`, so the same node serves both `workflow.run(\"ORD-…\")` and `workflow.as_agent(...).run(\"ORD-…\")`.\n3. **`FileCheckpointStorage` is type-allow-listed.** Every user dataclass must appear in `allowed_checkpoint_types=[\"__main__:OrderRecord\", \"fulfillment_workflow:OrderRecord\", …]` under **all** module names you ship under (typically `__main__` for the CLI run plus the importable module name when another LAB imports the file). Without this, resume fails with `Checkpoint deserialization blocked for type '…'`. Pattern:\n\n   ```python\n   user_types = (\"OrderRecord\", \"StockReport\", \"AllocationPlan\", ...)\n   allowed = sorted({\n       f\"{m}:{t}\"\n       for m in (__name__, \"__main__\", \"fulfillment_workflow\")\n       for t in user_types\n   })\n   storage = FileCheckpointStorage(storage_path=\".checkpoints\",\n                                   allowed_checkpoint_types=allowed)\n   ```\n\n4. **Drain the stream after `request_info`.** Checkpoints are flushed at the **end** of each super-step. If you `break` out of `async for event in workflow.run(..., stream=True)` the moment you see `request_info`, the post-super-step checkpoint that carries the pending request is never written, and the subsequent resume raises `No pending requests found in workflow context`. Collect `pending[event.request_id] = event.data` and let the iterator end naturally — the workflow has paused, there is nothing left to do.\n\n5. **A paused workflow instance cannot be re-run.** After pausing on `request_info`, the original `Workflow` still has `_is_running=True`, so calling `workflow.run(...)` on it again raises `Workflow is already running. Concurrent executions are not allowed.`. Build a fresh instance via your `build_workflow()` factory (same checkpoint dir, same allow-list) and call `fresh_wf.run(checkpoint_id=..., responses={req_id: reply})` in **one** call — this single entry point covers both restore and send-responses.\n\n6. **`event.source_executor_id` raises for non-`request_info` events.** It is a property that throws `RuntimeError` for every event type except `\"request_info\"`. Use `event.executor_id` everywhere; it is always defined when the event has a source.\n\n7. **No `ConcurrentBuilder` for typed executors.** `ConcurrentBuilder` is designed for fanning a `list[ChatMessage]` user prompt out to multiple chat agents and merging back as `list[ChatMessage]`. To fan a typed dataclass (e.g. `OrderRecord`) out to multiple `@executor` functions and merge via `list[LegResult]`, build the DAG explicitly:\n\n   ```python\n   WorkflowBuilder(start_executor=intake,\n                   checkpoint_storage=storage,\n                   name=\"ZavaFulfillment\",\n                   output_from=[finance, approval])\n       .add_fan_out_edges(intake, [stock_check, shipping_quote])\n       .add_fan_in_edges([stock_check, shipping_quote], allocator)\n       .add_edge(allocator, approval)\n       .add_edge(approval, dispatch)\n       .add_edge(dispatch, finance)\n       .build()\n   ```\n\n   `checkpoint_storage=` is a **kwarg** on `WorkflowBuilder` — there is no `.with_checkpoint_storage(...)` method.\n\nBonus shape rules surfaced by the same LAB: `Message(\"user\", \"hello\")` iterates the string into per-character `Content` objects — pass `Message(\"user\", [\"hello\"])`; read text via `m.text`. And `WorkflowEvent.type` is the discriminator (`\"executor_invoked\"`, `\"output\"`, `\"request_info\"`, `\"superstep_completed\"`), **not** `event.kind`.\n\n","# Microsoft Agent Framework — Workflows (Python)\n\nBuild deterministic, durable, observable multi-agent \u002F multi-step pipelines on top of the Microsoft Agent Framework Python SDK. Workflows are a graph of `Executor` nodes connected by typed edges; agents are first-class executors; events stream as the graph runs; state can be checkpointed and replayed.\n\n## Architecture\n\n```\n        ┌──────────────────────────────────────────────────────────────┐\n        │                       WorkflowBuilder                        │\n        │  start_executor=A, output_from=[...], intermediate_output…   │\n        └─────────────┬────────────────────────────────────────────────┘\n                      │ .build()\n                      ▼\n               ┌────────────┐\n               │  Workflow  │   ──►  .run(input)  \u002F  .run(stream=True)\n               └─────┬──────┘                       \u002F  .run(checkpoint_id=…)\n                     │                              \u002F  .run(responses={…})\n                     ▼\n        ┌────────────────────────────┐\n        │  Executor graph (DAG)      │\n        │   • Executor \u002F @executor   │\n        │   • Agent \u002F AgentExecutor  │   events: executor_invoked,\n        │   • WorkflowExecutor (sub) │           executor_completed,\n        │   • Orchestration builders │           output, intermediate,\n        └────────┬───────────────────┘           request_info, status,\n                 │                                superstep_completed\n                 ▼\n        ┌────────────────────────────┐\n        │  WorkflowContext (per run) │   ctx.send_message \u002F ctx.yield_output\n        │  state, requests, events   │   ctx.request_info \u002F ctx.set_state\n        └────────────────────────────┘\n```\n\nTwo equivalent authoring styles:\n\n| Style | When to use | Entry points |\n| --- | --- | --- |\n| **Graph (declarative)** | Branching, loops, fan-out\u002Ffan-in, orchestration patterns, sub-workflows, checkpointing | `Executor`, `@executor`, `@handler`, `WorkflowBuilder` |\n| **Functional** | Linear or simply branched pipelines expressed as plain async Python | `@workflow`, `@step`, `ctx.request_info` |\n\n## Installation\n\n```bash\n# Core workflows ship inside agent-framework \u002F agent-framework-core\npip install agent-framework --pre\n\n# Optional: graph visualization (WorkflowViz.export → SVG)\npip install \"agent-framework[viz]\" --pre\n# Also install GraphViz binary: https:\u002F\u002Fgraphviz.org\u002Fdownload\u002F\n\n# Optional: orchestration builders ship inside the core package; or pin directly\npip install agent-framework-orchestrations --pre\n```\n\nOrchestration builders are imported from the `agent_framework.orchestrations` submodule:\n\n```python\nfrom agent_framework.orchestrations import (\n    SequentialBuilder,\n    ConcurrentBuilder,\n    HandoffBuilder,\n    GroupChatBuilder,\n    MagenticBuilder,\n)\n```\n\n## Environment Variables\n\nWorkflow samples that embed an `Agent` use `FoundryChatClient` (lightweight, project-backed; no server-side agent lifecycle):\n\n| Variable | Purpose |\n| --- | --- |\n| `FOUNDRY_PROJECT_ENDPOINT` | Azure AI Foundry Agent Service (V2) project endpoint |\n| `FOUNDRY_MODEL` | Model deployment name in your Foundry project |\n\nAuthenticate via `azure.identity.AzureCliCredential()` (run `az login` first), or substitute any `SupportsChatGetResponse` chat client (OpenAI, Azure OpenAI, etc.).\n\n## Core Workflow\n\n### 1. Executors and edges\n\nAn `Executor` is a node. Inputs and outputs are typed. Handlers receive a `WorkflowContext[T_Out, T_W_Out]`:\n\n- `ctx.send_message(value)` — forward to downstream nodes\n- `ctx.yield_output(value)` — produce a workflow output (terminal yield, but workflows complete when **idle**, not on first yield)\n- `ctx.set_state(key, value)` \u002F `ctx.get_state(key)` — per-run scratchpad\n- `ctx.request_info(...)` — pause for human \u002F external input\n\nTwo equivalent ways to declare a node:\n\n```python\nfrom agent_framework import Executor, WorkflowBuilder, WorkflowContext, executor, handler\nfrom typing_extensions import Never\n\n\nclass UpperCase(Executor):\n    @handler\n    async def to_upper(self, text: str, ctx: WorkflowContext[str]) -> None:\n        await ctx.send_message(text.upper())\n\n\n@executor(id=\"reverse_text\")\nasync def reverse_text(text: str, ctx: WorkflowContext[Never, str]) -> None:\n    await ctx.yield_output(text[::-1])\n\n\ndef create_workflow():\n    upper = UpperCase(id=\"upper\")\n    return (\n        WorkflowBuilder(start_executor=upper)\n        .add_edge(upper, reverse_text)\n        .build()\n    )\n```\n\nRun it:\n\n```python\nworkflow = create_workflow()\nevents = await workflow.run(\"hello world\")\nprint(events.get_outputs())           # ['DLROW OLLEH']\nprint(events.get_final_state())       # WorkflowRunState.IDLE\n```\n\n> **State isolation**: wrap construction in a helper (`create_workflow()`) and call it per run so executor instance state never leaks between runs.\n\n### 2. Agents as workflow nodes\n\n`Agent` objects (and `AgentExecutor`-wrapped agents) plug directly into the graph. Inputs are normally `AgentExecutorRequest`; outputs are `AgentExecutorResponse`. When the start node is an `Agent`, the framework converts `run(str)` \u002F `run(Message(...))` to a request automatically.\n\n```python\nfrom agent_framework import Agent, WorkflowBuilder\nfrom agent_framework.foundry import FoundryChatClient\nfrom azure.identity import AzureCliCredential\n\nclient = FoundryChatClient(\n    project_endpoint=os.environ[\"FOUNDRY_PROJECT_ENDPOINT\"],\n    model=os.environ[\"FOUNDRY_MODEL\"],\n    credential=AzureCliCredential(),\n)\nwriter = Agent(client=client, instructions=\"You write copy.\", name=\"writer\")\nreviewer = Agent(client=client, instructions=\"You review copy.\", name=\"reviewer\")\n\nworkflow = WorkflowBuilder(start_executor=writer).add_edge(writer, reviewer).build()\nevents = await workflow.run(\"Slogan for an affordable electric SUV.\")\n```\n\n### 3. Streaming events\n\n`workflow.run(..., stream=True)` returns an async iterator of `WorkflowEvent`:\n\n| `event.type` | Meaning |\n| --- | --- |\n| `executor_invoked` \u002F `executor_completed` | Lifecycle per node — observable without code changes |\n| `output` | A `ctx.yield_output(...)` value (or agent streaming token via `AgentResponseUpdate`) |\n| `intermediate` | A yield from an executor selected by `intermediate_output_from` |\n| `request_info` | A `ctx.request_info(...)` call — workflow is paused |\n| `status` | `WorkflowRunState.IDLE`, `IDLE_WITH_PENDING_REQUESTS`, etc. |\n| `superstep_completed` | A graph \"super-step\" finished (safe checkpoint boundary) |\n\n```python\nasync for event in workflow.run(initial, stream=True):\n    if event.type == \"output\":\n        print(\"OUT:\", event.data)\n    elif event.type == \"intermediate\":\n        print(\"…  \", event.executor_id, event.data)\n```\n\n## Output selection (`output_from` \u002F `intermediate_output_from`)\n\n`WorkflowBuilder` exposes which executor yields are surfaced. `output_from` is an **allow-list** for Workflow Output, not a routing rule; unselected yields are hidden unless `intermediate_output_from` selects them.\n\n| Selection | Workflow Output (`type='output'`) | Intermediate (`type='intermediate'`) | Hidden |\n| --- | --- | --- | --- |\n| Omit both | All `yield_output` (deprecation warning) | — | — |\n| `output_from=\"all\"` | Every output-capable executor | — | — |\n| `output_from=[answerer]` | Only `answerer` | — | All others |\n| `output_from=[answerer], intermediate_output_from=\"all_other\"` | `answerer` | Every other output-capable executor | — |\n| `intermediate_output_from=\"all_other\"` | Builder-internal default executors only | Every output-capable executor | Builder plumbing |\n| `output_from=[w], intermediate_output_from=[r, rv]` | `w` | `r`, `rv` | Any others |\n\nInvalid: `output_from=\"all_other\"`, `intermediate_output_from=\"all\"`, the same executor in both lists, duplicates, unknown executors, or both lists empty. Deprecated alias: `output_executors` (use `output_from`).\n\nWhen the workflow is exposed via `workflow.as_agent()`, Workflow Output becomes the `AgentResponse.text`; Intermediate Output becomes `text_reasoning` content on the response messages.\n\n## Functional API (`@workflow`, `@step`)\n\nWrite workflows as plain async Python — no graph, no edges. Use normal `if`\u002F`else`, loops, and `asyncio.gather` for branching and parallelism.\n\n```python\nfrom agent_framework import workflow, step\n\nasync def fetch(url: str) -> dict: ...\nasync def transform(d: dict) -> str: ...\n\n@workflow\nasync def pipeline(url: str) -> str:\n    raw = await fetch(url)\n    summary = await transform(raw)\n    return f\"[OK] {summary}\"\n\nresult = await pipeline.run(\"https:\u002F\u002Fexample.com\")\nprint(result.get_outputs()[0])\n\n# Streaming events\nstream = pipeline.run(\"https:\u002F\u002Fexample.com\", stream=True)\nasync for event in stream:\n    if event.type == \"output\":\n        print(event.data)\nfinal = await stream.get_final_response()\n```\n\nAdd `@step` to a function to cache its result across HITL resume or checkpoint restore — without `@step` every call re-executes from the top on resume. `@step` composes with `asyncio.gather` for cached parallel branches. To enable checkpointing, pass `checkpoint_storage` to `@workflow`:\n\n```python\nfrom agent_framework import InMemoryCheckpointStorage, step, workflow\n\nstorage = InMemoryCheckpointStorage()\n\n@step\nasync def fetch_data(url: str) -> dict: ...\n\n@workflow(checkpoint_storage=storage)\nasync def pipeline(url: str) -> str:\n    raw = await fetch_data(url)\n    ...\n```\n\nFunctional workflows also support `as_agent()`, HITL via `ctx: RunContext` + `ctx.request_info()`, and structured `RunContext` access — all opt-in.\n\n## Methods (graph API quick reference)\n\n| Builder method | Purpose |\n| --- | --- |\n| `WorkflowBuilder(start_executor=, checkpoint_storage=, max_iterations=, output_from=, intermediate_output_from=)` | Create a builder |\n| `.add_edge(src, dst, condition=None, target_id=None)` | Connect two executors; optional predicate over the upstream message |\n| `.add_chain([a, b, c])` | Sugar for sequential edges |\n| `.add_fan_out_edges(src, [t1, t2, ...])` | Same message to many targets in parallel; requires at least two targets |\n| `.add_fan_in_edges([s1, s2, ...], dst)` | Collect a `list[T]` from many sources into one handler; requires at least two sources |\n| `.add_switch_case_edge_group(src, [Case(condition=, target=), Default(target=)])` | One-of-N routing |\n| `.add_multi_selection_edge_group(src, targets, selection_func=)` | Dynamic subset fan-out |\n| `.build()` → `Workflow` | Materialize the graph |\n\n| Workflow method | Purpose |\n| --- | --- |\n| `await workflow.run(message, stream=False)` | Run; returns `WorkflowRunResult` (or async iterator if `stream=True`) |\n| `workflow.run(checkpoint_id=...)` | Resume from a saved checkpoint |\n| `workflow.run(responses={request_id: value})` | Resume after `request_info` |\n| `result.get_outputs()` | List of `type='output'` payloads |\n| `result.get_final_state()` | `WorkflowRunState` enum |\n| `result.get_request_info_events()` | Pending HITL requests |\n| `workflow.as_agent(\"agent_id\")` | Wrap workflow as an `Agent` |\n\n| Context method | Purpose |\n| --- | --- |\n| `ctx.send_message(msg, target_id=None)` | Forward to downstream (optionally one specific target) |\n| `ctx.yield_output(value)` | Emit a workflow output (subject to `output_from` selection) |\n| `ctx.set_state(key, value)` \u002F `ctx.get_state(key)` | Per-run state |\n| `await ctx.request_info(request_data=..., response_type=..., request_id=None)` | Pause for human \u002F external input |\n| `@response_handler` | Method that receives the resumed response |\n\n## Complete Example — Spam triage with conditional routing\n\n```python\nimport asyncio\nimport os\nfrom typing import Any\nfrom pydantic import BaseModel\nfrom typing_extensions import Never\n\nfrom agent_framework import (\n    Agent, AgentExecutor, AgentExecutorRequest, AgentExecutorResponse,\n    Message, WorkflowBuilder, WorkflowContext, executor,\n)\nfrom agent_framework.foundry import FoundryChatClient\nfrom azure.identity import AzureCliCredential\n\n\nclass Detection(BaseModel):\n    is_spam: bool\n    reason: str\n    email_content: str\n\n\nclass EmailReply(BaseModel):\n    response: str\n\n\ndef is_spam(expected: bool):\n    def cond(msg: Any) -> bool:\n        if not isinstance(msg, AgentExecutorResponse):\n            return True\n        try:\n            return Detection.model_validate_json(msg.agent_response.text).is_spam == expected\n        except Exception:\n            return False\n    return cond\n\n\n@executor(id=\"to_reply_request\")\nasync def to_reply_request(resp: AgentExecutorResponse, ctx: WorkflowContext[AgentExecutorRequest]) -> None:\n    parsed = Detection.model_validate_json(resp.agent_response.text)\n    await ctx.send_message(AgentExecutorRequest(\n        messages=[Message(\"user\", contents=[parsed.email_content])],\n        should_respond=True,\n    ))\n\n\n@executor(id=\"send_reply\")\nasync def send_reply(resp: AgentExecutorResponse, ctx: WorkflowContext[Never, str]) -> None:\n    parsed = EmailReply.model_validate_json(resp.agent_response.text)\n    await ctx.yield_output(f\"Sent:\\n{parsed.response}\")\n\n\n@executor(id=\"handle_spam\")\nasync def handle_spam(resp: AgentExecutorResponse, ctx: WorkflowContext[Never, str]) -> None:\n    parsed = Detection.model_validate_json(resp.agent_response.text)\n    await ctx.yield_output(f\"Spam: {parsed.reason}\")\n\n\ndef make_agent(name: str, instructions: str, fmt: type[BaseModel]) -> Agent:\n    return Agent(\n        client=FoundryChatClient(\n            project_endpoint=os.environ[\"FOUNDRY_PROJECT_ENDPOINT\"],\n            model=os.environ[\"FOUNDRY_MODEL\"],\n            credential=AzureCliCredential(),\n        ),\n        instructions=instructions,\n        name=name,\n        default_options={\"response_format\": fmt},\n    )\n\n\nasync def main() -> None:\n    detector = AgentExecutor(make_agent(\n        \"spam_detector\",\n        \"Detect spam. Reply JSON {is_spam, reason, email_content}.\",\n        Detection,\n    ))\n    writer = AgentExecutor(make_agent(\n        \"email_writer\",\n        \"Draft a professional reply. Reply JSON {response}.\",\n        EmailReply,\n    ))\n\n    workflow = (\n        WorkflowBuilder(start_executor=detector)\n        .add_edge(detector, to_reply_request, condition=is_spam(False))\n        .add_edge(to_reply_request, writer)\n        .add_edge(writer, send_reply)\n        .add_edge(detector, handle_spam, condition=is_spam(True))\n        .build()\n    )\n\n    request = AgentExecutorRequest(\n        messages=[Message(\"user\", contents=[\"Hi team, follow-up notes from today’s meeting…\"])],\n        should_respond=True,\n    )\n    result = await workflow.run(request)\n    print(result.get_outputs()[0])\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n```\n\n## Orchestration patterns (quick reference)\n\n| Pattern | Builder | What it does |\n| --- | --- | --- |\n| Sequential | `SequentialBuilder(participants=[...])` | Chain agents with a shared conversation context |\n| Concurrent | `ConcurrentBuilder(participants=[...])` | Fan-out to all participants in parallel, default aggregator returns combined `Messages` |\n| Handoff | `HandoffBuilder(participants=[...]).with_start_agent(...)` | Mesh routing; auto-injected handoff tools; `HandoffAgentUserRequest` for HITL |\n| GroupChat | `GroupChatBuilder(participants=[...]).with_orchestrator(agent=)` | Manager (agent or function) picks the next speaker each round |\n| Magentic | `MagenticBuilder(participants=[...], manager_agent=, max_round_count=)` | Multi-agent planner\u002Fexecutor with progress ledger, plan review, checkpointing |\n\nEach builder exposes `.build()` → `Workflow`, plus `.as_agent()` to expose the whole orchestration as a single agent. All builders honor the same `output_from` \u002F `intermediate_output_from` selection model.\n\n`HandoffBuilder` requires every participant agent to set `require_per_service_call_history_persistence=True`, or `.build()` raises `ValueError`.\n\n## Human-in-the-loop (`ctx.request_info`)\n\n```python\nfrom agent_framework import Executor, WorkflowContext, handler, response_handler\nfrom dataclasses import dataclass\n\n@dataclass\nclass HumanFeedback:\n    prompt: str\n\nclass TurnManager(Executor):\n    @handler\n    async def ask(self, _: str, ctx: WorkflowContext) -> None:\n        await ctx.request_info(\n            request_data=HumanFeedback(prompt=\"Approve? (yes\u002Fno)\"),\n            response_type=str,\n        )\n\n    @response_handler\n    async def on_feedback(self, original: HumanFeedback, reply: str, ctx: WorkflowContext[Never, str]) -> None:\n        await ctx.yield_output(f\"User said: {reply}\")\n```\n\n```python\n# Drive the loop:\nstream = workflow.run(\"start\", stream=True)\npending = await collect_request_info(stream)\nwhile pending:\n    answers = {req_id: input(req.prompt) for req_id, req in pending.items()}\n    stream = workflow.run(stream=True, responses=answers)\n    pending = await collect_request_info(stream)\n```\n\nFor agent-level tool approvals, set `approval_mode=\"always_require\"` on a `@tool`; the workflow then emits `request_info` events whose `data` is a `FunctionApprovalRequestContent` — reply with `data.to_function_approval_response(approved=True)`.\n\n## Checkpointing\n\nPlug a checkpoint storage into `WorkflowBuilder(checkpoint_storage=...)` (graph) or `@workflow(checkpoint_storage=...)` (functional). A checkpoint is written after each super-step.\n\n| Storage | Use |\n| --- | --- |\n| `InMemoryCheckpointStorage()` | Tests, demos |\n| `FileCheckpointStorage(storage_path=...)` | Local dev, multi-process recovery |\n| `CosmosCheckpointStorage(...)` | Durable production checkpoints in Azure Cosmos DB NoSQL |\n\n```python\nstorage = InMemoryCheckpointStorage()\nworkflow = (\n    WorkflowBuilder(start_executor=start, checkpoint_storage=storage)\n    .add_edge(start, worker).add_edge(worker, worker)  # self-loop\n    .build()\n)\n\n# Resume from a checkpoint\nlatest = await storage.get_latest(workflow_name=workflow.name)\nevents = workflow.run(checkpoint_id=latest.checkpoint_id, stream=True)\n```\n\nPer-executor state survives a restart by overriding `on_checkpoint_save` \u002F `on_checkpoint_restore`:\n\n```python\nfrom typing import Any\nfrom typing_extensions import override\n\nclass Worker(Executor):\n    def __init__(self, id: str) -> None:\n        super().__init__(id=id)\n        self._cache: dict[int, list[tuple[int, int]]] = {}\n\n    @override\n    async def on_checkpoint_save(self) -> dict[str, Any]:\n        return {\"cache\": self._cache}\n\n    @override\n    async def on_checkpoint_restore(self, state: dict[str, Any]) -> None:\n        self._cache = state.get(\"cache\", {})\n```\n\n## Sub-workflows and `workflow.as_agent()`\n\nEmbed a built `Workflow` as a node via `WorkflowExecutor(child, id=\"child\")`:\n\n```python\nfrom agent_framework import WorkflowExecutor\n\nchild = build_validation_workflow()\nparent = (\n    WorkflowBuilder(start_executor=orchestrator)\n    .add_edge(orchestrator, WorkflowExecutor(child, id=\"validate\"))\n    .add_edge(\"validate\", delivery)\n    .build()\n)\n```\n\nA child's `ctx.request_info(...)` bubbles up as a `SubWorkflowRequestMessage` to the parent — handle it in the parent and reply with `request.create_response(value)` to intercept (no propagation to the caller).\n\nExpose any workflow as an agent:\n\n```python\nagent = workflow.as_agent(\"my_agent\")\nresponse = await agent.run(\"user prompt\")\nprint(response.text)                   # Workflow Output\n# Intermediate Output is preserved as text_reasoning content on response.messages\n```\n\nThis unlocks the reflection pattern (Worker ↔ Reviewer loop wrapped as a single agent), and lets a workflow act as a sub-agent inside another workflow.\n\n## Conventions\n\n- **One executor = one responsibility.** Keep handlers small; transform → forward.\n- **Carry typed payloads on edges**, not raw strings. Use `@dataclass` \u002F Pydantic for structured handoffs.\n- **Use `response_format`** on agents that drive routing — never parse free text.\n- **Store large payloads in `ctx.set_state`** and pass small references (IDs) on edges.\n- **Helper-build pattern**: wrap construction in `def create_workflow(): ...` so each run gets fresh executor instances.\n- **Single-target map pattern**: if one executor emits many messages to one downstream executor, use `.add_edge(src, dst)` rather than `.add_fan_out_edges(src, [dst])`. If those messages must become one batch before the next stage, use a stateful `Executor` with `@handler` to buffer and forward once complete.\n- **Pick explicit `output_from`** in new code; compatibility mode emits a deprecation warning.\n- **`@step` only what's expensive.** Cheap functions can stay un-decorated and replay freely.\n- **Treat participant names as stable identifiers** — especially for Magentic checkpoints.\n\n## Best Practices\n\n- Build the graph once, call `workflow.run(...)` many times; runs share workflow but not executor state when you use the helper-build pattern.\n- Use `stream=True` for any long-running or interactive workflow so you can render progress and capture `request_info`.\n- Combine `@workflow(checkpoint_storage=...)` + `@step` to get durable, replayable pipelines with minimal boilerplate.\n- For HITL: emit a typed `request_data` dataclass so the caller has a contract, not a string blob.\n- For sub-workflows that emit `request_info`, decide up front whether the parent intercepts (handle `SubWorkflowRequestMessage`) or the outer caller receives the request.\n- For orchestration builders, pick `output_from` deliberately — Sequential keeps the last participant by default; Concurrent \u002F GroupChat \u002F Magentic keep their aggregator\u002Fmanager outputs.\n- For visualization, build with `[viz]` extra and call `WorkflowViz(workflow).to_mermaid()` \u002F `.to_digraph()` \u002F `.export(format=\"svg\")`.\n- Validate agent JSON with Pydantic — defensive parsing prevents dead routes.\n\n## Reference Files\n\n| File | Topic |\n| --- | --- |\n| [references\u002Fgetting_started.md](references\u002Fgetting_started.md) | Executors, edges, agents in a workflow, streaming |\n| [references\u002Fcontrol_flow.md](references\u002Fcontrol_flow.md) | Conditions, switch-case, multi-selection, loops, intermediate vs terminal outputs |\n| [references\u002Fparallelism.md](references\u002Fparallelism.md) | Fan-out\u002Ffan-in, mixed-type aggregation, map-reduce, visualization |\n| [references\u002Ffunctional_api.md](references\u002Ffunctional_api.md) | `@workflow`, `@step`, streaming, HITL, agents in functional pipelines |\n| [references\u002Fhuman_in_the_loop.md](references\u002Fhuman_in_the_loop.md) | `ctx.request_info`, approvals, declaration-only tools |\n| [references\u002Fcheckpointing.md](references\u002Fcheckpointing.md) | In-memory \u002F file \u002F Cosmos storage, resume, sub-workflow + HITL checkpoints |\n| [references\u002Forchestrations.md](references\u002Forchestrations.md) | `SequentialBuilder`, `ConcurrentBuilder`, `HandoffBuilder`, `GroupChatBuilder`, `MagenticBuilder` |\n| [references\u002Fcomposition.md](references\u002Fcomposition.md) | `WorkflowExecutor`, sub-workflow request interception, `workflow.as_agent()`, shared sessions, kwargs |\n\n## Workshop-verified gotchas (LAB 04)\n\nThese are the seven traps observed while building the ZavaShop fulfillment workflow in [`workshop\u002FLAB04-fulfillment-workflow\u002Ffulfillment_workflow.py`](..\u002F..\u002F..\u002Fworkshop\u002FLAB04-fulfillment-workflow\u002Ffulfillment_workflow.py). Every bullet has been reproduced against `agent-framework 1.0.0rc3`.\n\n1. **No `from __future__ import annotations`** in modules that use `@response_handler`. The validator (`_request_info_mixin.py`) inspects the **raw** `WorkflowContext[X, Y]` annotation; with deferred annotations it sees a `str` instead of the resolved generic and raises `Invalid handler signature`. Keep workflow modules annotation-eager.\n2. **`workflow.as_agent()` always dispatches `list[Message]`** to the start executor — it asserts `is_type_compatible(list[Message], start.input_types)`. If your true intake input is `str`, write a class-based `IntakeExecutor(Executor)` with **two** `@handler` methods (`str` *and* `list[Message]`) that share a private `_emit`, so the same node serves both `workflow.run(\"ORD-…\")` and `workflow.as_agent(...).run(\"ORD-…\")`.\n3. **`FileCheckpointStorage` is type-allow-listed.** Every user dataclass must appear in `allowed_checkpoint_types=[\"__main__:OrderRecord\", \"fulfillment_workflow:OrderRecord\", …]` under **all** module names you ship under (typically `__main__` for the CLI run plus the importable module name when another LAB imports the file). Without this, resume fails with `Checkpoint deserialization blocked for type '…'`. Pattern:\n\n   ```python\n   user_types = (\"OrderRecord\", \"StockReport\", \"AllocationPlan\", ...)\n   allowed = sorted({\n       f\"{m}:{t}\"\n       for m in (__name__, \"__main__\", \"fulfillment_workflow\")\n       for t in user_types\n   })\n   storage = FileCheckpointStorage(storage_path=\".checkpoints\",\n                                   allowed_checkpoint_types=allowed)\n   ```\n\n4. **Drain the stream after `request_info`.** Checkpoints are flushed at the **end** of each super-step. If you `break` out of `async for event in workflow.run(..., stream=True)` the moment you see `request_info`, the post-super-step checkpoint that carries the pending request is never written, and the subsequent resume raises `No pending requests found in workflow context`. Collect `pending[event.request_id] = event.data` and let the iterator end naturally — the workflow has paused, there is nothing left to do.\n\n5. **A paused workflow instance cannot be re-run.** After pausing on `request_info`, the original `Workflow` still has `_is_running=True`, so calling `workflow.run(...)` on it again raises `Workflow is already running. Concurrent executions are not allowed.`. Build a fresh instance via your `build_workflow()` factory (same checkpoint dir, same allow-list) and call `fresh_wf.run(checkpoint_id=..., responses={req_id: reply})` in **one** call — this single entry point covers both restore and send-responses.\n\n6. **`event.source_executor_id` raises for non-`request_info` events.** It is a property that throws `RuntimeError` for every event type except `\"request_info\"`. Use `event.executor_id` everywhere; it is always defined when the event has a source.\n\n7. **No `ConcurrentBuilder` for typed executors.** `ConcurrentBuilder` is designed for fanning a `list[ChatMessage]` user prompt out to multiple chat agents and merging back as `list[ChatMessage]`. To fan a typed dataclass (e.g. `OrderRecord`) out to multiple `@executor` functions and merge via `list[LegResult]`, build the DAG explicitly:\n\n   ```python\n   WorkflowBuilder(start_executor=intake,\n                   checkpoint_storage=storage,\n                   name=\"ZavaFulfillment\",\n                   output_from=[finance, approval])\n       .add_fan_out_edges(intake, [stock_check, shipping_quote])\n       .add_fan_in_edges([stock_check, shipping_quote], allocator)\n       .add_edge(allocator, approval)\n       .add_edge(approval, dispatch)\n       .add_edge(dispatch, finance)\n       .build()\n   ```\n\n   `checkpoint_storage=` is a **kwarg** on `WorkflowBuilder` — there is no `.with_checkpoint_storage(...)` method.\n\nBonus shape rules surfaced by the same LAB: `Message(\"user\", \"hello\")` iterates the string into per-character `Content` objects — pass `Message(\"user\", [\"hello\"])`; read text via `m.text`. And `WorkflowEvent.type` is the discriminator (`\"executor_invoked\"`, `\"output\"`, `\"request_info\"`, `\"superstep_completed\"`), **not** `event.kind`.\n\n",{"name":5,"license":15,"metadata":35,"description":7},{"author":12,"package":36,"version":37},"agent-framework","1.0.0","main","586d2a6053b9ed89e357a3fd26204b1efd6f593c",".github\u002Fskills\u002Fagent-framework-workflows-py",25,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002FLearn-Microsoft-Agent-Framework-with-Foundry-ZavaShop-Supply-Chain-Workshop","https:\u002F\u002Fgithub.com\u002Fmicrosoft","https:\u002F\u002Fopensource.microsoft.com","Open source projects and samples from Microsoft","2026-05-27 15:18:36",{"source":8,"skillName":5,"commands":48,"prompt":58,"zipAvailable":59},[49,52,55],{"tool":50,"command":51},"npx","npx skills add microsoft\u002FLearn-Microsoft-Agent-Framework-with-Foundry-ZavaShop-Supply-Chain-Workshop --skill agent-framework-workflows-py",{"tool":53,"command":54},"bunx","bunx skills add microsoft\u002FLearn-Microsoft-Agent-Framework-with-Foundry-ZavaShop-Supply-Chain-Workshop --skill agent-framework-workflows-py",{"tool":56,"command":57},"pnpm","pnpm dlx skills add microsoft\u002FLearn-Microsoft-Agent-Framework-with-Foundry-ZavaShop-Supply-Chain-Workshop --skill agent-framework-workflows-py","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002FLearn-Microsoft-Agent-Framework-with-Foundry-ZavaShop-Supply-Chain-Workshop\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fagent-framework-workflows-py\u002FSKILL.md",true,[61,67,71,75,79,83,87,91],{"path":62,"resourceType":63,"resourceTypeDesc":64,"sizeBytes":65,"sourceHtmlUrl":66},"references\u002Fcheckpointing.md","REFERENCE","参考文档",6522,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002FLearn-Microsoft-Agent-Framework-with-Foundry-ZavaShop-Supply-Chain-Workshop\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fagent-framework-workflows-py\u002Freferences\u002Fcheckpointing.md",{"path":68,"resourceType":63,"resourceTypeDesc":64,"sizeBytes":69,"sourceHtmlUrl":70},"references\u002Fcomposition.md",10187,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002FLearn-Microsoft-Agent-Framework-with-Foundry-ZavaShop-Supply-Chain-Workshop\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fagent-framework-workflows-py\u002Freferences\u002Fcomposition.md",{"path":72,"resourceType":63,"resourceTypeDesc":64,"sizeBytes":73,"sourceHtmlUrl":74},"references\u002Fcontrol_flow.md",5524,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002FLearn-Microsoft-Agent-Framework-with-Foundry-ZavaShop-Supply-Chain-Workshop\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fagent-framework-workflows-py\u002Freferences\u002Fcontrol_flow.md",{"path":76,"resourceType":63,"resourceTypeDesc":64,"sizeBytes":77,"sourceHtmlUrl":78},"references\u002Ffunctional_api.md",6499,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002FLearn-Microsoft-Agent-Framework-with-Foundry-ZavaShop-Supply-Chain-Workshop\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fagent-framework-workflows-py\u002Freferences\u002Ffunctional_api.md",{"path":80,"resourceType":63,"resourceTypeDesc":64,"sizeBytes":81,"sourceHtmlUrl":82},"references\u002Fgetting_started.md",5405,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002FLearn-Microsoft-Agent-Framework-with-Foundry-ZavaShop-Supply-Chain-Workshop\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fagent-framework-workflows-py\u002Freferences\u002Fgetting_started.md",{"path":84,"resourceType":63,"resourceTypeDesc":64,"sizeBytes":85,"sourceHtmlUrl":86},"references\u002Fhuman_in_the_loop.md",5958,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002FLearn-Microsoft-Agent-Framework-with-Foundry-ZavaShop-Supply-Chain-Workshop\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fagent-framework-workflows-py\u002Freferences\u002Fhuman_in_the_loop.md",{"path":88,"resourceType":63,"resourceTypeDesc":64,"sizeBytes":89,"sourceHtmlUrl":90},"references\u002Forchestrations.md",7667,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002FLearn-Microsoft-Agent-Framework-with-Foundry-ZavaShop-Supply-Chain-Workshop\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fagent-framework-workflows-py\u002Freferences\u002Forchestrations.md",{"path":92,"resourceType":63,"resourceTypeDesc":64,"sizeBytes":93,"sourceHtmlUrl":94},"references\u002Fparallelism.md",6068,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002FLearn-Microsoft-Agent-Framework-with-Foundry-ZavaShop-Supply-Chain-Workshop\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fagent-framework-workflows-py\u002Freferences\u002Fparallelism.md",[96,113,127,142,158],{"skillNo":97,"name":98,"nameZh":6,"description":99,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":100,"categories":101,"keywords":108,"publishedAt":30},"01M69AV5QEKJXJ66BP6GZ83ZYE","agent-framework-workflows-csharp","Build deterministic, multi-step workflows with the Microsoft Agent Framework .NET SDK (Microsoft.Agents.AI.Workflows). Use when composing executors and edges into a DAG, running agents as graph nodes, doing fan-out\u002Ffan-in concurrency, conditional routing, shared state, streaming events, checkpoint\u002Fresume, human-in-the-loop request ports, or higher-level orchestration patterns (sequential, concurrent, handoff, group chat, Magentic). Covers WorkflowBuilder, AgentWorkflowBuilder, IWorkflowContext, CheckpointManager, and the StreamingRun event loop.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002FLearn-Microsoft-Agent-Framework-with-Foundry-ZavaShop-Supply-Chain-Workshop\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fagent-framework-workflows-csharp\u002FSKILL.md",[102,103],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":104,"categoryName":105,"taxonomyType":106,"taxonomyTypeDesc":107},"01JBSEEDCAT000000000000038","人力资源","PROFESSION","适用职业",[109,25,24,110,111,112,28,9],"agent framework workflows csharp","handoff","sdk","dag",{"skillNo":114,"name":115,"nameZh":6,"description":116,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":117,"categories":118,"keywords":120,"publishedAt":30},"01M69AV2AV22E3GXDSC5GC80RK","agent-framework-azure-ai-py","Build Azure AI Foundry agents using the Microsoft Agent Framework Python SDK (agent-framework-azure-ai). Use when creating persistent agents with AzureAIAgentsProvider, using hosted tools (code interpreter, file search, web search), integrating MCP servers, managing conversation threads, or implementing streaming responses. Covers function tools, structured outputs, and multi-tool agents.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002FLearn-Microsoft-Agent-Framework-with-Foundry-ZavaShop-Supply-Chain-Workshop\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fagent-framework-azure-ai-py\u002FSKILL.md",[119],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[121,122,123,24,124,9,125,111,27,126],"agent framework azure ai py","azure","azure ai","web","foundry","MCP",{"skillNo":128,"name":129,"nameZh":6,"description":130,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":131,"categories":132,"keywords":137,"publishedAt":30},"01M69ATZ0523NG0E1EJ1AFE6N4","agent-framework-azure-ai-csharp","Build Microsoft Foundry agents with the Microsoft Agent Framework .NET SDK (Microsoft.Agents.AI + Microsoft.Agents.AI.Foundry). Use when creating agents via AIProjectClient.AsAIAgent or FoundryAgent, calling function tools and hosted tools (code interpreter, file search, web search, memory search), integrating MCP servers (local McpClient and hosted ResponseTool.CreateMcpTool), consuming Foundry Toolboxes via MCP, managing AgentSession multi-turn conversations, producing structured output with ChatResponseFormat.ForJsonSchema, attaching FoundryMemoryProvider, or shipping reusable AgentInlineSkill \u002F AgentClassSkill bundles via AgentSkillsProvider.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002FLearn-Microsoft-Agent-Framework-with-Foundry-ZavaShop-Supply-Chain-Workshop\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fagent-framework-azure-ai-csharp\u002FSKILL.md",[133,134],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":135,"categoryName":136,"taxonomyType":106,"taxonomyTypeDesc":107},"01JBSEEDCAT000000000000030","开发工程师",[123,138,122,24,139,140,125,9,124,141,111,126],"agent framework azure ai csharp","foundry toolboxes","microsoft foundry","memory",{"skillNo":143,"name":144,"nameZh":6,"description":145,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":146,"categories":147,"keywords":155,"publishedAt":30},"01M69ATW1JPFQZV0R6GGGZQTT7","agent-framework-agui-py","Build AG-UI (Agent UI) protocol servers and clients in Python with the Microsoft Agent Framework (`agent-framework-ag-ui`). Use when hosting an `Agent` or `Workflow` as a streaming HTTP endpoint via `add_agent_framework_fastapi_endpoint`, consuming an AG-UI server with `AGUIChatClient`, wiring hybrid client-side + server-side tool calls, managing thread-scoped state and conversation continuity, returning rich tool payloads via `state_update`, or implementing the seven standard AG-UI features (chat, backend tool rendering, human-in-the-loop, generative UI, tool-based UI, shared state, predictive state updates).","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002FLearn-Microsoft-Agent-Framework-with-Foundry-ZavaShop-Supply-Chain-Workshop\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fagent-framework-agui-py\u002FSKILL.md",[148,151,152],{"categoryNo":149,"categoryName":150,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000010","后端与接口",{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":153,"categoryName":154,"taxonomyType":106,"taxonomyTypeDesc":107},"01JBSEEDCAT000000000000032","设计师",[156,24,157,27,29,9],"agent framework agui py","http",{"skillNo":159,"name":160,"nameZh":6,"description":161,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":162,"categories":163,"keywords":170,"publishedAt":30},"01M69ATRJ4YQH0KVP7ZE6Z1VN5","agent-framework-agui-csharp","Build AG-UI protocol clients and servers with the Microsoft Agent Framework .NET SDK (Microsoft.Agents.AI.AGUI + Microsoft.Agents.AI.Hosting.AGUI.AspNetCore). Use when exposing an AIAgent over HTTP with server-sent events, building remote agent clients, supporting frontend tool rendering, agentic generative UI, predictive state updates, shared state, or human-in-the-loop UX. Covers AGUIChatClient, MapAGUI, AddAGUI, session storage, DelegatingAIAgent, and the AgentResponseUpdate streaming contract.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002FLearn-Microsoft-Agent-Framework-with-Foundry-ZavaShop-Supply-Chain-Workshop\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fagent-framework-agui-csharp\u002FSKILL.md",[164,167,168,169],{"categoryNo":165,"categoryName":166,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发",{"categoryNo":149,"categoryName":150,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":153,"categoryName":154,"taxonomyType":106,"taxonomyTypeDesc":107},[171,24,172,157,9,173,111,174],"agent framework agui csharp","agentic","storage","frontend"]