{
  "markdown": "# ChainWeaver\n\n<!-- mcp-name: io.github.dgenio/chainweaver -->\n\n**Find where your agent no longer needs to reason. Review the evidence. Turn the accepted path into a governed deterministic capability.**\n\n[![PyPI](https://img.shields.io/pypi/v/chainweaver)](https://pypi.org/project/chainweaver/)\n[![CI](https://github.com/dgenio/ChainWeaver/actions/workflows/ci.yml/badge.svg)](https://github.com/dgenio/ChainWeaver/actions/workflows/ci.yml)\n[![Python](https://img.shields.io/pypi/pyversions/chainweaver)](https://pypi.org/project/chainweaver/)\n[![License](https://img.shields.io/github/license/dgenio/ChainWeaver)](LICENSE)\n[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/dgenio/ChainWeaver/blob/main/notebooks/quickstart.ipynb)\n[![Read the Weaver Stack overview on Towards AI](https://img.shields.io/badge/Read_the_overview-Towards_AI-black?logo=medium&logoColor=white)](https://pub.towardsai.net/the-weaver-stack-one-contract-layer-for-safe-llm-agents-7f733cad5eac)\n\n<p align=\"center\">\n  <img src=\"docs/assets/quickstart.svg\" alt=\"ChainWeaver quick start: pip install, run a flow, and see the LLM-free step log\" width=\"760\">\n</p>\n\n**Product thesis under validation — observe → prove → review → compile.**\nChainWeaver can inspect repeated tool behavior, surface candidates, and execute\nreviewed deterministic paths with typed contracts. Deterministic execution by\nitself is **not** the moat: if you already know the exact workflow, a normal\nPython function, LangGraph node, or provider-native tool may be simpler. The\nhypothesis being tested is that **trace-derived evidence, useful rejection,\ngoverned promotion, security-boundary preservation, and drift detection** make\nChainWeaver worth adopting. See [Product validation & adoption\ngates](docs/product-validation.md) and [#553](https://github.com/dgenio/ChainWeaver/issues/553).\n\n**Remove reasoning boundaries, never security boundaries.** Compiling several\ntool calls into one capability must not silently aggregate privileges or erase\nchild approval requirements. That invariant is tracked explicitly in\n[#554](https://github.com/dgenio/ChainWeaver/issues/554).\n\n**Governance for deterministic tool paths.** Typed I/O at every step,\nfile-serializable flows, schema-drift detection, determinism *attestation*,\nproperty fuzzing, and structured audit traces provide a disciplined execution\nsubstrate for paths that have actually earned deterministic promotion.\n\n> **Benchmarks are evidence about the executor, not proof of product-market fit.**\n> The repo's [benchmark report](benchmarks/results/latest.md) is reproducible —\n> regenerate it yourself with `python benchmarks/report.py` — and shows the\n> deterministic core avoiding model-mediated transitions in its synthetic\n> comparison. It does **not** establish that every repeated path should\n> be compiled, or that ChainWeaver beats the obvious plain-Python implementation.\n> The independent validation program requires that manual baseline explicitly.\n\n```python\nfrom chainweaver import Tool, Flow, FlowStep, FlowRegistry, FlowExecutor\n# (NumberInput, ValueOutput, double_fn defined in full example below)\n\n# 1. Wrap any function as a schema-validated Tool\ndouble = Tool(name=\"double\", description=\"Doubles a number.\",\n              input_schema=NumberInput, output_schema=ValueOutput, fn=double_fn)\n# 2. Wire tools into a Flow\nflow = Flow(name=\"calc\", description=\"Double a number.\",\n            steps=[FlowStep(tool_name=\"double\", input_mapping={\"number\": \"number\"})])\n# 3. Register and execute — zero LLM calls\nregistry = FlowRegistry()\nregistry.register_flow(flow)\nexecutor = FlowExecutor(registry=registry)\nexecutor.register_tool(double)\nresult = executor.execute_flow(\"calc\", {\"number\": 5})\n# result.final_output → {\"number\": 5, \"value\": 10}\n```\n\n> See the [full example](#quick-start) below or run `python examples/simple_linear_flow.py`\n\n**[Installation](#installation) · [Why ChainWeaver?](#why-chainweaver) · [Is this for me?](#is-this-for-me) · [Product validation](docs/product-validation.md) · [Quick Start](#quick-start) · [Architecture](#architecture) · [Docs site](https://chainweaver.readthedocs.io/) · [Roadmap](#roadmap)**\n\n---\n\n## See it in 30 seconds\n\nThe deterministic executor solves a simple problem: once a path has been shown\nto need no intermediate reasoning, stop paying a model to re-decide the same\nplumbing on every run.\n\n**Before — a model-mediated path:**\n\n```\nturn 1   ─►  LLM(\"plan\")    ─►  search(query)         ─► 12 results\nturn 2   ─►  LLM(\"next?\")   ─►  extract(results)      ─► 8 facts\nturn 3   ─►  LLM(\"next?\")   ─►  validate(facts)       ─► 7 facts\nturn 4   ─►  LLM(\"next?\")   ─►  format(facts)         ─► answer\n```\n\n**After review — the accepted path can run deterministically:**\n\n```\nturn 1   ─►  LLM(\"plan\")    ─►  search_summarize_flow(query)\n                                  └─ search ─► extract ─► validate ─► format\n```\n\nThe agent still decides *which* capability to invoke. The deterministic steps\ninside it run with strict Pydantic validation and no LLM involvement.\n\nThe harder product question comes **before** this diagram: should this path be\ncompiled at all? A useful ChainWeaver analysis must be able to show why a\ncandidate is recurrent and structurally safe **and** reject paths where semantic\njudgment, side effects, authorization, or approval boundaries still matter. That\nclaim is currently being tested on independent traces in #553.\n\n**Copy-paste executor path:**\n\n```bash\npip install 'chainweaver[yaml]'\npython examples/simple_linear_flow.py\n```\n\nThe summary below is a condensed view of the real `ExecutionResult` the script\nproduces:\n\n```\nflow=double_add_format success=True\nfinal_output={'number': 5, 'value': 20, 'result': 'Final value: 20'}\nstep 0 double          {'value': 10}\nstep 1 add_ten         {'value': 20}\nstep 2 format_result   {'result': 'Final value: 20'}\n```\n\n---\n\n## Why ChainWeaver?\n\n### Why not just write a Python function?\n\nOften, you should.\n\nIf your team already knows the workflow is fixed, a normal function or the\nworkflow primitives in your existing framework are usually the lowest-complexity\nanswer. ChainWeaver should earn another dependency only when its lifecycle adds\nmeaningful value—for example:\n\n- discovering non-obvious repeated model-mediated paths from real traces;\n- showing evidence for recurrence, dataflow compatibility, and counterexamples;\n- rejecting tempting paths that still require semantic judgment;\n- preserving approval and authorization constraints during promotion;\n- producing reproducible review evidence and artifact identity;\n- detecting schema/safety/policy drift after promotion;\n- exporting or executing the accepted capability without pretending that fewer\n  model calls automatically means greater correctness.\n\nWhether those advantages are strong enough in real teams is a **falsifiable\nproduct hypothesis**, not a README assumption. See\n[docs/product-validation.md](docs/product-validation.md).\n\nWhen an LLM-powered agent routes tools together — `fetch_data → transform → store` — a\ncommon pattern is to insert an LLM call between steps so the model can decide\nwhat to do next. For a path that has been **demonstrated and reviewed as fully\ndeterministic**, those intermediate calls can add latency, cost, and variability\nwithout adding useful judgment.\n\nChainWeaver's executor can run an accepted deterministic path without any LLM\ninvolvement between steps:\n\n```\nUser request\n    │\n    ▼\nFlowExecutor ──► Tool A ──► Tool B ──► Tool C\n    │\n    ▼\nResponse\n```\n\n| Criterion | Model-mediated path | ChainWeaver deterministic path |\n|---|---|---|\n| LLM calls between deterministic steps | potentially one or more | 0 |\n| Reproducibility | depends on model decisions | deterministic path |\n| Schema validation | framework/application dependent | Pydantic enforced |\n| Observability | framework/application dependent | structured step logs |\n| Reusability | application dependent | registered, versioned flows |\n\n### How is this different from LangChain / LangGraph / Prefect / Dagster / Temporal?\n\nThose frameworks can also execute deterministic code. ChainWeaver should **not**\nbe selected because deterministic execution is impossible elsewhere. Its current\nproduct thesis is narrower: start from observed agent/tool behavior, establish\nwhich regions no longer need reasoning, make the evidence and rejections\nreviewable, then promote accepted paths into governed deterministic\ncapabilities.\n\nThe execution substrate remains deliberately small and LLM-free between steps,\nbut the project is testing whether the evidence/governance lifecycle—not the\nmere existence of another workflow runtime—is the part users value.\n\nSee [docs/comparisons.md](docs/comparisons.md) for the detailed, versioned\ncomparison and [docs/product-validation.md](docs/product-validation.md) for the\ncriteria that can falsify this positioning.\n\n---\n\n## Is this for me?\n\nChainWeaver is built for one specific shape of problem. The\n[full fit/non-fit page](https://chainweaver.readthedocs.io/en/latest/boundaries/) covers\nthe nuances; the short version:\n\n**Use ChainWeaver when**\n\n- You have real agent/tool traces and suspect parts of the path are repeated\n  plumbing rather than useful model judgment.\n- You want evidence and review around **which** paths deserve deterministic\n  promotion, not only a runtime for a workflow you already know.\n- Determinism, strict schemas, auditability, and drift detection matter once a\n  path is promoted.\n- You are prepared to keep security and approval boundaries explicit rather\n  than treating a macro-tool invocation as blanket child authorization.\n\n**Don't use ChainWeaver when**\n\n- You already know the workflow and a normal Python function or your existing\n  framework expresses it clearly enough.\n- Every step requires open-ended reasoning to pick the next one (use an agent\n  framework: LangGraph, the OpenAI / Anthropic SDK tool-use loops).\n- You need a general workflow engine for scheduled / durable jobs across time\n  (use Prefect, Dagster, or Temporal).\n- You expect the executor to call an LLM. It deliberately doesn't.\n- You cannot preserve the authorization/approval semantics of a side-effecting\n  path during compilation.\n\nThe product thesis, validation protocol, and kill/pivot criteria are public in\n[docs/product-validation.md](docs/product-validation.md).\n\nFor the correctness argument behind the deterministic execution design, see\n[docs/data-integrity.md](docs/data-integrity.md).\n\n### Part of the Weaver Stack\n\nChainWeaver is the **deterministic multi-step tool execution** layer of the\n[Weaver Stack](https://github.com/dgenio/weaver-spec) — a family of small,\ncomposable SDKs that share `weaver-spec`'s `SelectableItem` routing contract.\nOn the request path a router picks *which* capability to invoke, ChainWeaver\nruns the deterministic tool path *behind* it, and downstream layers gate and\nguard the call:\n\n```mermaid\nflowchart LR\n    req([Request]) --> ctx[contextweaver<br/>context assembly]\n    ctx --> cw[<b>ChainWeaver</b><br/>deterministic flow execution]\n    cw --> ak[agent-kernel<br/>capability gating]\n    ak --> af[agentfence<br/>runtime guardrails]\n    subgraph adjacent [Adjacent · use any subset]\n        vg[vibeguard]\n        lw[lessonweaver]\n        se[skdr-eval]\n    end\n```\n\n**Use standalone or together.** Each layer stands on its own — ChainWeaver's\nbase install has **no hard dependency** on any sibling and works fully\nstandalone. Real interop runs through the `chainweaver[weaver-stack]` extra,\nwhich pins the published [`weaver-contracts`](https://pypi.org/project/weaver-contracts/)\npackage: ChainWeaver consumes its `SelectableItem` / `RoutingDecision` /\n`CapabilityToken` types directly, so a router can hand a routing decision\nstraight to `resolve_flow_from_routing_decision()` for deterministic\nexecution. See the runnable\n[Weaver Stack golden path](examples/weaver_stack_golden_path/) (issue #234).\n\n| Layer | What it owns | Sibling project |\n|-------|--------------|-----------------|\n| Routing / capability selection | \"Which named operation handles this request?\" | `weaver-spec` (#91 — `SelectableItem` contract) |\n| Context assembly | \"What facts and tool descriptions belong in the prompt?\" | `contextweaver` (#106) |\n| Agent kernel | The model-mediated tool-use loop itself | `agent-kernel` (#89) |\n| **Deterministic flow execution** | \"Run this exact tool sequence with strict schemas, no LLM between steps\" | **ChainWeaver — this repo** |\n| Lessons & evaluation | Turning traces into reviewed operational guidance ([how ChainWeaver feeds it](docs/lessons-from-traces.md)) | `lessonweaver` (#210) |\n\nChainWeaver does **not** replace an agent framework.  It is meant to be\ncalled *from* one — see the [LangGraph\nrecipe](docs/cookbook/langgraph-node.md) (issue #205) and the [OpenAI Agents\nSDK recipe](docs/cookbook/openai-agents-tool.md) (issue #206) for\nthe canonical integration patterns.\n\nFor host-level expectations (when to invoke, how to store traces,\nside-effect tools, MCP parity), see the\n[Runtime responsibilities](docs/runtime-responsibilities.md) page.\n\n---\n\n## Installation\n\n```bash\npip install chainweaver                  # base install — no extras\npip install 'chainweaver[yaml]'          # most common — needed for .flow.yaml files\npip install 'chainweaver[yaml,otel,mcp]' # combine extras with commas\n```\n\nThe base install pulls only five runtime dependencies (`deepdiff`,\n`packaging`, `pydantic`, `tenacity`, `typer`) and has no transitive LLM\nSDK pinned.  Pick extras for the integrations you actually use:\n\n| Extra | Use when | Pulls in |\n|-------|----------|----------|\n| `chainweaver[yaml]` | Reading / writing `.flow.yaml` flow files (the CLI's `run`, `validate`, `check`, `doctor` commands need this) | `pyyaml` |\n| `chainweaver[otel]` | Emitting OpenTelemetry spans for every flow run | `opentelemetry-api` |\n| `chainweaver[mcp]` | Exposing flows over MCP via the `chainweaver.mcp` adapter | `mcp` |\n| `chainweaver[contrib]` | Importing the curated standard tool library (see [Standard tool library](#standard-tool-library)) | *(no extra deps today)* |\n| `chainweaver[langchain]` | Bidirectional adapters between ChainWeaver and LangChain `BaseTool` | `langchain-core` |\n| `chainweaver[llamaindex]` | Bidirectional adapters between ChainWeaver and LlamaIndex `FunctionTool` | `llama-index-core` |\n| `chainweaver[test]` | Hypothesis-based property tests for your own flows | `hypothesis`, `hypothesis-jsonschema` |\n| `chainweaver[docs]` | Building the docs site locally with mkdocs | `mkdocs`, `mkdocs-material`, `mkdocstrings` |\n| `chainweaver[weaver-stack]` | Real Weaver Stack interop — consuming the shared routing/capability contract (`weaver-spec` #91, `contextweaver` #106, `agent-kernel` #89, #233) | `weaver-contracts` |\n| `chainweaver[integrations]` | Every integration extra above at once — the composition CI exercises | the union of the integration rows above |\n\nMaintainer tooling (pytest, ruff, mypy, nbmake, ...) is **not** a published\nextra: it lives in PEP 735 dependency groups, installed with\n`pip install -e \".[integrations]\" --group dev` (#550). The `[dev]` extra no\nlonger exists.\n\nPackage metadata (`pyproject.toml`) publishes URLs for the\n[documentation](https://chainweaver.readthedocs.io/), the\n[source](https://github.com/dgenio/ChainWeaver), the\n[changelog](https://github.com/dgenio/ChainWeaver/blob/main/CHANGELOG.md),\nand the\n[issue tracker](https://github.com/dgenio/ChainWeaver/issues), so `pip\nshow chainweaver` and the PyPI sidebar point users to the right place.\n\n---\n\n## Quick Start\n\n### Define tools, build a flow, and execute it\n\n<!-- smoke-test: run -->\n```python\nfrom pydantic import BaseModel\nfrom chainweaver import Tool, Flow, FlowStep, FlowRegistry, FlowExecutor\n\n# --- 1. Declare schemas ---\n\nclass NumberInput(BaseModel):\n    number: int\n\nclass ValueOutput(BaseModel):\n    value: int\n\nclass ValueInput(BaseModel):\n    value: int\n\nclass FormattedOutput(BaseModel):\n    result: str\n\n# --- 2. Implement tool functions ---\n\ndef double_fn(inp: NumberInput) -> dict:\n    return {\"value\": inp.number * 2}\n\ndef add_ten_fn(inp: ValueInput) -> dict:\n    return {\"value\": inp.value + 10}\n\ndef format_result_fn(inp: ValueInput) -> dict:\n    return {\"result\": f\"Final value: {inp.value}\"}\n\n# --- 3. Wrap as Tool objects ---\n\ndouble_tool = Tool(\n    name=\"double\",\n    description=\"Takes a number and returns its double.\",\n    input_schema=NumberInput,\n    output_schema=ValueOutput,\n    fn=double_fn,\n)\n\nadd_ten_tool = Tool(\n    name=\"add_ten\",\n    description=\"Takes a value and returns value + 10.\",\n    input_schema=ValueInput,\n    output_schema=ValueOutput,\n    fn=add_ten_fn,\n)\n\nformat_tool = Tool(\n    name=\"format_result\",\n    description=\"Formats a numeric value into a human-readable string.\",\n    input_schema=ValueInput,\n    output_schema=FormattedOutput,\n    fn=format_result_fn,\n)\n\n# --- 4. Define the flow ---\n\nflow = Flow(\n    name=\"double_add_format\",\n    description=\"Doubles a number, adds 10, and formats the result.\",\n    steps=[\n        FlowStep(tool_name=\"double\",        input_mapping={\"number\": \"number\"}),\n        FlowStep(tool_name=\"add_ten\",       input_mapping={\"value\": \"value\"}),\n        FlowStep(tool_name=\"format_result\", input_mapping={\"value\": \"value\"}),\n    ],\n)\n\n# --- 5. Execute ---\n\nregistry = FlowRegistry()\nregistry.register_flow(flow)\n\nexecutor = FlowExecutor(registry=registry)\nexecutor.register_tool(double_tool)\nexecutor.register_tool(add_ten_tool)\nexecutor.register_tool(format_tool)\n\nresult = executor.execute_flow(\"double_add_format\", {\"number\": 5})\n\nprint(result.success)       # True\nprint(result.final_output)  # {'number': 5, 'value': 20, 'result': 'Final value: 20'}\n\nfor record in result.execution_log:\n    print(record.step_index, record.tool_name, record.outputs)\n# 0 double {'value': 10}\n# 1 add_ten {'value': 20}\n# 2 format_result {'result': 'Final value: 20'}\n```\n\nYou can also run the bundled examples directly:\n\n```bash\npython examples/simple_linear_flow.py   # simple arithmetic flow\npython examples/etl_flow.py             # ETL flow: fetch → validate → normalize → enrich → store\npython examples/mcp_search_flow.py      # MCP-style search → extract → format flow\npython examples/naive_vs_compiled.py    # timing comparison: naive LLM calls vs ChainWeaver flow\npython examples/coding_agent_pr_review.py    # deterministic PR-review checklist\npython examples/coding_agent_changelog.py    # changelog generation workflow template\npython examples/coding_agent_debug_log.py    # debug-log triage workflow template\npython examples/mcp_style_before_after_demo.py        # before/after MCP-style flow demo\npython examples/release_readiness_flow/release_readiness.py  # deterministic release-readiness gate\npython examples/skdr_policy_eval_flow.py              # offline policy-evaluation workflow template\npython examples/integrations/langgraph_node.py        # call a flow from a LangGraph node (needs chainweaver[langgraph])\npython examples/integrations/openai_agents_tool.py    # expose a flow as an OpenAI Agents SDK tool (needs chainweaver[openai-agents])\n```\n\nThe hosted docs also include a [cookbook](docs/cookbook/index.md) with paired\nscripts under `examples/cookbook/`, plus framework recipes and workflow\ntemplates (LangGraph, OpenAI Agents SDK, release-readiness, policy evaluation).\n\n### With the `@tool` decorator\n\nThe `@tool` decorator eliminates boilerplate by introspecting type hints to\nauto-generate input schemas:\n\n<!-- smoke-test: run -->\n```python\nfrom pydantic import BaseModel\nfrom chainweaver import tool, Flow, FlowStep, FlowRegistry, FlowExecutor\n\nclass ValueOutput(BaseModel):\n    value: int\n\nclass FormattedOutput(BaseModel):\n    result: str\n\n@tool(description=\"Doubles a number.\")\ndef double(number: int) -> ValueOutput:\n    return {\"value\": number * 2}\n\n@tool(description=\"Adds ten.\")\ndef add_ten(value: int) -> ValueOutput:\n    return {\"value\": value + 10}\n\n@tool(description=\"Formats the result.\")\ndef format_result(value: int) -> FormattedOutput:\n    return {\"result\": f\"Final value: {value}\"}\n\nflow = Flow(\n    name=\"double_add_format\",\n    description=\"Doubles a number, adds 10, and formats.\",\n    steps=[\n        FlowStep(tool_name=\"double\",        input_mapping={\"number\": \"number\"}),\n        FlowStep(tool_name=\"add_ten\",       input_mapping={\"value\": \"value\"}),\n        FlowStep(tool_name=\"format_result\", input_mapping={\"value\": \"value\"}),\n    ],\n)\n\nregistry = FlowRegistry()\nregistry.register_flow(flow)\n\nexecutor = FlowExecutor(registry=registry)\nexecutor.register_tool(double)\nexecutor.register_tool(add_ten)\nexecutor.register_tool(format_result)\n\nresult = executor.execute_flow(\"double_add_format\", {\"number\": 5})\nprint(result.final_output)  # {'number': 5, 'value': 20, 'result': 'Final value: 20'}\n```\n\nDecorated tools are also directly callable:\n\n```python\nprint(double(number=5))  # {'value': 10}\n```\n\nSee `examples/decorator_tool.py` for a runnable before/after comparison.\n\n### With `FlowBuilder`\n\n`FlowBuilder` provides a fluent, chainable API as a more Pythonic alternative\nto constructing `Flow` objects directly.  It produces an identical `Flow` — it\nis syntax sugar, not a replacement:\n\n```python\nfrom chainweaver import FlowBuilder\n\nflow = (\n    FlowBuilder(\"double_add_format\", \"Doubles a number, adds 10, and formats.\")\n    .step(\"double\", number=\"number\")\n    .step(\"add_ten\", value=\"value\")\n    .step(\"format_result\", value=\"value\")\n    .build()\n)\n```\n\n- **`.step(tool_name, **mapping)`** — adds a step; string values are context-key\n  lookups, non-string values are literal constants, no kwargs = full-context\n  passthrough.\n- **`.step_from(flow_step)`** — appends a pre-built `FlowStep` for interop.\n- **`.with_input_schema(Model)`** / **`.with_output_schema(Model)`** — optional\n  flow-level Pydantic schema declarations.\n- **`.with_trigger(conditions)`** — optional free-form trigger metadata.\n- **`.build()`** — returns a validated `Flow`; raises `FlowBuilderError` if\n  `name` or `description` is missing.\n\n---\n\n## Interactive playground\n\nWant to try ChainWeaver without installing anything locally? The\n[`playground/`](playground/) directory ships a Streamlit app that lets you pick\na pre-loaded flow, edit its JSON input, run it, and watch the step-by-step,\n**LLM-free** execution trace with a Mermaid diagram — the same `FlowExecutor`\nthe library ships.\n\n```bash\npip install -r playground/requirements.txt\nstreamlit run playground/app.py\n```\n\nIt ships three example flows (arithmetic, a data flow, and an MCP-style\nsearch), produces shareable `?share=<token>` links that round-trip a run through\nthe URL, and is fully stateless so it deploys to Streamlit Community Cloud with\nno backend. See [`playground/README.md`](playground/README.md) for local-run and\ndeployment instructions.\n\n---\n\n## Architecture\n\n```\nchainweaver/\n├── __init__.py       # Public API\n├── builder.py        # FlowBuilder — fluent API for flow construction\n├── compat.py         # schema_fingerprint, check_flow_compatibility\n├── compiler.py       # compile_flow — static schema flow validation\n├── decorators.py     # @tool decorator for zero-boilerplate tool definition\n├── tools.py          # Tool — named callable with Pydantic schemas\n├── flow.py           # FlowStep + Flow + FlowStatus — ordered step definitions\n├── registry.py       # FlowRegistry — multi-version flow catalogue\n├── executor.py       # FlowExecutor — deterministic, LLM-free runner\n├── exceptions.py     # Typed exceptions with traceable context\n└── log_utils.py      # Structured per-step logging\n```\n\n### Core abstractions\n\n#### `Tool`\n\n```python\nTool(\n    name=\"my_tool\",\n    description=\"...\",\n    input_schema=MyInputModel,   # Pydantic BaseModel\n    output_schema=MyOutputModel, # Pydantic BaseModel\n    fn=my_callable,\n)\n```\n\nA tool wraps a plain Python callable together with Pydantic models for strict\ninput/output validation.\n\n#### `FlowStep`\n\n```python\nFlowStep(\n    tool_name=\"my_tool\",\n    input_mapping={\n        \"key_for_tool\": \"key_from_context\",   # flat top-level lookup\n        \"city\": \"/user/address/city\",         # RFC-6901 pointer into nested context\n        \"limit\": 10,                          # non-string -> literal constant\n    },\n    output_mapping={\"renamed\": \"value\"},      # rename/prune outputs before merge\n)\n```\n\n`input_mapping` maps keys from the accumulated execution context into the\ntool's input schema. String values are looked up in the context — a plain key\nis a top-level lookup, and a string starting with `/` is an RFC-6901 JSON\npointer into the nested context (#387) — while non-string values are literal\nconstants.\n\n`output_mapping` (#386) optionally renames and prunes a tool's outputs before\nthey merge into the context: `{context_key: output_key}` keeps only the listed\noutput keys, each renamed. Omit it to merge every output key verbatim.\n\nTo inject per-request secrets that must never appear in a model-visible schema\n(auth tokens, account numbers), pass them at execute-time instead of in\n`initial_input`:\n\n```python\nresult = executor.execute_flow(\n    \"account_overview\",\n    {\"query\": \"what's my balance?\"},          # LLM-visible\n    dynamic_params={\"billingAccountNumber\": \"1.60007029\"},  # hidden, injected (#316)\n)\n```\n\n#### `Flow`\n\n```python\nFlow(\n    name=\"my_flow\",\n    version=\"0.1.0\",             # SemVer string; defaults to \"0.1.0\" if omitted\n    description=\"...\",\n    steps=[step_a, step_b, step_c],\n    deterministic=True,          # metadata annotation; executor is always LLM-free\n    trigger_conditions={\"intent\": \"process data\"},  # optional metadata\n)\n```\n\nAn ordered sequence of steps. See [AGENTS.md](AGENTS.md) §5 for the full\nfield table (`status`, `tool_schema_hashes`, and the `input_schema_ref` /\n`output_schema_ref` string fields with their resolved-property accessors).\n\nA `FlowStep` runs **either** a tool (`tool_name`) **or** a registered\nsub-flow (`flow_name`) — exactly one, never both. Referencing a sub-flow lets\nyou compose reusable flows (issue #75):\n\n```python\nfetch_validate = Flow(\n    name=\"fetch_validate\",\n    description=\"Fetch and validate.\",\n    steps=[\n        FlowStep(tool_name=\"fetch\", input_mapping={\"url\": \"url\"}),\n        FlowStep(tool_name=\"validate\", input_mapping={\"data\": \"data\"}),\n    ],\n)\nfetch_then_transform = Flow(\n    name=\"fetch_then_transform\",\n    description=\"Reuse fetch_validate, then transform.\",\n    steps=[\n        FlowStep(flow_name=\"fetch_validate\", input_mapping={\"url\": \"url\"}),  # sub-flow\n        FlowStep(tool_name=\"transform\", input_mapping={\"data\": \"data\"}),\n    ],\n)\n```\n\nThe executor runs the sub-flow with the step's resolved inputs, merges its\noutput back into the parent context, and attaches the sub-flow's\n`ExecutionResult` to the parent `StepRecord.sub_result`. Sub-flow references\nare checked for cycles and a configurable max nesting depth\n(`FlowExecutor(max_composition_depth=...)`, default 10) before execution,\nraising `FlowCompositionError` otherwise.\n\nA `deadline` or `CancellationToken` passed to `execute_flow` is forwarded into\ncomposed sub-flows, so cancellation and the wall-clock budget are observed at\nthe step boundaries *inside* a sub-flow — a long sub-flow stops between its own\nsteps rather than only at the parent boundary. The cost report's\n`steps_executed` counts the tool invocations a composed step actually drove\n(recursively), so `llm_calls_avoided` reflects every tool that ran across the\ncomposition.\n\n#### `FlowRegistry`\n\n```python\nregistry = FlowRegistry()\nregistry.register_flow(flow)\nregistry.get_flow(\"my_flow\")\nregistry.list_flows()\nregistry.match_flow_by_intent(\"process data\")  # basic substring match\n```\n\nAn in-memory catalogue of flows.\n\n#### `FlowExecutor`\n\n```python\nexecutor = FlowExecutor(registry=registry)\nexecutor.register_tool(tool_a)\nresult = executor.execute_flow(\"my_flow\", {\"key\": \"value\"})\n\n# Version-targeted execution: run an exact registered version instead of the\n# latest. Omitting `version` keeps the default (latest) behaviour. The version\n# that actually ran is always recorded on `result.flow_version`, so routing,\n# audit, and replay can correlate a result with the precise flow definition.\nresult = executor.execute_flow(\"my_flow\", {\"key\": \"value\"}, version=\"1.2.0\")\nassert result.flow_version == \"1.2.0\"\n```\n\nRuns a flow step-by-step with full schema validation and structured logging.\n**No LLM calls are made at any point.**\n\n#### `ChainAnalyzer`\n\n```python\nfrom chainweaver import ChainAnalyzer, ToolChain\n\nanalyzer = ChainAnalyzer(tools=[tool_a, tool_b, tool_c])\n\n# All schema-compatible pairs\nmatrix: dict[str, list[str]] = analyzer.compatibility_matrix()\n\n# All valid tool sequences up to length 3\nchains: list[ToolChain] = analyzer.find_chains(max_depth=3)\n\n# Filter by start or end tool\nchains = analyzer.find_chains(max_depth=3, start=\"tool_a\", end=\"tool_c\")\n\n# Promote chains to ready-to-register Flow objects\nflows = analyzer.suggest_flows(max_depth=3, min_depth=2)\n```\n\nDiscovers schema-compatible tool combinations **offline**, before any flow is\nregistered or executed. `compatibility_matrix()` checks that every required\ninput field of a consumer tool appears in the output of the producer with a\nmatching type. `suggest_flows()` auto-wires `input_mapping` by name-matching\nand returns `Flow` objects ready for `FlowRegistry.register_flow()`.\n\n### Data flow\n\n```\ninitial_input (dict)\n       │\n       ▼\n ┌─────────────────────────────────────────────┐\n │  Execution context (cumulative dict)        │\n │                                             │\n │  Step 0: resolve inputs → run tool → merge  │\n │  Step 1: resolve inputs → run tool → merge  │\n │  Step N: resolve inputs → run tool → merge  │\n └─────────────────────────────────────────────┘\n       │\n       ▼\n ExecutionResult.final_output (merged context)\n```\n\n---\n\n## MCP Integration Concept\n\nChainWeaver can sit between agent/tool observation and deterministic execution:\n\n```\nAgent / tool traces\n   │  (observe repeated paths)\n   ▼\nCandidate analysis + human review\n   │  (prove/reject; preserve security boundaries)\n   ▼\nGoverned deterministic capability\n   │  (FlowExecutor and/or supported export)\n   ▼\nMCP / host-framework invocation\n```\n\nMCP is an interoperability surface, not the product category. The current\nruntime can expose reviewed flows as MCP tools, while #555 explores whether\nportable outputs should let the same approved capability execute through other\nhosts without requiring ChainWeaver to own the runtime.\n\nChainWeaver is **a library you embed**, not the runtime that owns your trace\nstore, identity system, or enterprise authorization control plane. Host authors\nshould read [`docs/runtime-responsibilities.md`](docs/runtime-responsibilities.md).\n\n---\n\n## Integrations\n\nChainWeaver plugs into the MCP ecosystem and major agent frameworks. Existing\nintegrations remain supported; new adapter breadth is deliberately lower\npriority than independent product validation.\n\n| Integration | What it does | Entry point |\n|---|---|---|\n| **MCP server** (outbound) | Expose your flows as MCP tools — agents call a whole compiled flow as one deterministic tool | [`chainweaver serve`](docs/cli.md) · [guide](docs/mcp-server.md) · [`FlowServer`](chainweaver/mcp/server.py) |\n| **MCP adapter** (inbound) | Wrap tools advertised by an MCP server as ChainWeaver `Tool`s | `chainweaver.mcp.MCPToolAdapter` |\n| **LangGraph** | Call a flow from a LangGraph node | [recipe](docs/cookbook/langgraph-node.md) · `examples/integrations/langgraph_node.py` |\n| **OpenAI Agents SDK** | Expose a flow as an Agents SDK `FunctionTool` | [recipe](docs/cookbook/openai-agents-tool.md) · `examples/integrations/openai_agents_tool.py` |\n| **LangChain / LlamaIndex** | Bidirectional tool bridges | `chainweaver.integrations.{langchain,llamaindex}` (see below) |\n| **OpenCode** | Observe tool runs, mine macro-flows, and expose reviewed flows back as MCP tools | [recipe](docs/cookbook/opencode-recipe.md) · [`chainweaver opencode`](chainweaver/cli/opencode.py) |\n| **Claude Code** | Capture `PostToolUse` hook traces, mine macro-flows, and expose reviewed flows back as MCP tools | [recipe](docs/cookbook/claude-code-recipe.md) · [`chainweaver claude`](chainweaver/cli/claude.py) |\n| **VS Code / Copilot** | Capture MCP tool traces (Copilot OTel) and expose reviewed flows via `.vscode/mcp.json` | [recipe](docs/cookbook/vscode-recipe.md) · [`chainweaver vscode`](chainweaver/cli/vscode.py) |\n| **GitHub Action** | Validate `.flow.yaml` / `.flow.json` files in CI with inline PR annotations | [`.github/actions/chainweaver`](.github/actions/chainweaver) · [guide](docs/github-action.md) |\n\nInstall the extra you need: `pip install 'chainweaver[mcp]'` (or `langgraph`,\n`openai-agents`, `langchain`, `llamaindex`). Importing any integration without its\nextra raises a clear `ImportError`.\n\nLooking to publish or list ChainWeaver in the MCP registry / awesome-lists / framework\ndirectories? See [`docs/distribution.md`](docs/distribution.md). Broad distribution\nis intentionally gated behind the naming decision (#556) and validation evidence\n(#553).\n\n---\n\n## Error Handling\n\nAll errors are typed and traceable:\n\n| Exception | When it is raised |\n|---|---|\n| `ToolNotFoundError` | A step references an unregistered tool |\n| `FlowNotFoundError` | The requested flow is not registered |\n| `FlowAlreadyExistsError` | Registering a flow that already exists (without `overwrite=True`) |\n| `FlowStatusError` | Executing a flow whose status is not `ACTIVE` (without `force=True`) |\n| `FlowCancelledError` | A `deadline` passed or a `CancellationToken` was cancelled at a step boundary (carries the partial result) |\n| `InvalidFlowVersionError` | A flow is registered with a version string that is not valid PEP 440 |\n| `FlowSerializationError` | A flow file (YAML/JSON) is malformed, has an unknown discriminator, or references an unresolvable class |\n| `SchemaValidationError` | Input or output fails Pydantic validation |\n| `InputMappingError` | A mapping key is not present in the context |\n| `FlowExecutionError` | The tool callable raises an unexpected exception |\n| `ApprovalDeniedError` | An execution-time approval callback denied a step, raised, or returned an invalid value — or `strict_safety=True` and a required-approval step has no callback |\n| `SafetyCeilingError` | A step's `ToolSafetyContract.side_effects` exceeds the executor's configured `max_side_effect_level` |\n| `GuardrailViolationError` | A registered `guardrail_callback` blocked a step at the input stage (content-safety / injection check) |\n| `ToolDefinitionError` | The `@tool` decorator cannot build a tool from a function |\n| `DAGDefinitionError` | A `DAGFlow` has a cycle, duplicate `step_id`, or unknown dependency |\n| `FlowCompositionError` | A composed flow has a sub-flow cycle, exceeds `max_composition_depth`, or references an unregistered sub-flow |\n| `ToolTimeoutError` | A `Tool` with `timeout_seconds` set exceeds the configured wall-clock cap |\n| `ToolOutputSizeError` | A `Tool` with `max_output_size` set returns an output larger than the configured cap |\n| `FlowBuilderError` | `FlowBuilder.build()` is called without a name or description |\n| `AttestationInputError` | The attestation input generator cannot synthesize a value for a schema field |\n| `PluginDiscoveryError` | Strict-mode plugin discovery (`discover_tools(strict=True)` / `discover_flows(strict=True)`) hits a misbehaving entry-point loader |\n| `ContribError` | A `chainweaver.contrib.tools` tool hits a contract violation (missing JSON-pointer key, wrong predicate shape, assertion mismatch) |\n| `FixtureStaleError` | A `record_then_replay` replay invocation cannot be matched to a recording (missing/stale fixture) |\n| `FuzzConfigError` | A property-based fuzzing run is misconfigured (no properties, `runs < 1`, a flow with no `input_schema` and no base input, or an unsupported input-field type) |\n| `CostProfileError` | A cost estimate is requested for a `(provider, model)` pair absent from the maintained `PROVIDER_PRICES` table |\n| `MCPMetadataError` | A server-provided MCP tool name fails the adapter's `MetadataPolicy` (and `on_invalid_name=\"error\"`) |\n| `MCPSchemaDriftError` | A pinned MCP tool's raw schema changed under `MCPToolAdapter(on_drift=\"error\")` |\n| `FlowAuthenticationError` | A network-exposed `FlowServer` authenticator returned `None` or raised; the call is refused before dispatch |\n| `RateLimitExceededError` | A `FlowServer` rate limiter declined the call |\n| `FlowAuthorizationError` | A `FlowServer` authorization callback denied the call (carries only a client-safe `reason_code`) |\n| `CheckpointVersionError` | A resumed snapshot's `snapshot_version` is an incompatible MAJOR relative to the running library |\n\nAll exceptions inherit from `ChainWeaverError` and carry a stable diagnostic\n`code` (e.g. `CW-E006`); the CLI prefixes it on error output and failing\n`StepRecord`s expose it as `error_code`. See the full code table in\n[docs/reference/error-table.md](docs/reference/error-table.md#stable-diagnostic-codes).\n\n---\n\n## Standard tool library\n\n`chainweaver.contrib.tools` ships a curated set of deterministic\nutility tools so that a new user can compose a meaningful flow on the\nfirst afternoon without writing any `Tool` boilerplate.\n\n```python\nfrom chainweaver.contrib.tools import (\n    assert_equal,\n    filter_list,\n    json_pluck,\n    json_set,\n    map_list,\n    passthrough,\n)\n```\n\n| Tool | Purpose |\n|------|---------|\n| `passthrough` | Identity — return the context unchanged. |\n| `json_pluck` | Extract one value by RFC-6901 JSON pointer. |\n| `json_set` | Set one value by RFC-6901 JSON pointer; returns a new dict. |\n| `assert_equal` | Raise `ContribError` when two context keys differ. |\n| `map_list` | Apply a registered sub-flow to each element of a list. |\n| `filter_list` | Drop elements whose predicate sub-flow returns falsy. |\n\nThe library is **deterministic-only**: no HTTP, file I/O, database\naccess, RNG, or clocks.  Anything stateful belongs in user code.\nInstall with `pip install 'chainweaver[contrib]'`.\n\nRunnable examples: [`examples/contrib_pluck_and_set.py`](examples/contrib_pluck_and_set.py),\n[`examples/contrib_map_filter.py`](examples/contrib_map_filter.py).\n\n---\n\n## Cost-avoided reporting\n\nEvery inter-step transition a naive agent delegates to an LLM is a routing\ncall ChainWeaver eliminates. `CostProfile` / `CostReport` turn that into a\ndollar estimate, and the maintained `PROVIDER_PRICES` table (dated snapshots,\nno live HTTP lookup) lets you price it against a real model:\n\n```python\nfrom chainweaver.cost import compute_cost_report\n\n# Build a profile straight from the maintained price table.\nreport = compute_cost_report(\n    steps_executed=6,                 # a six-tool flow\n    actual_execution_ms=4.2,\n    provider=\"anthropic\",\n    model=\"claude-opus-4-7\",\n)\nprint(report)\n```\n\n```text\nCost Avoided Report (estimate)\n──────────────────────────────\nSteps executed:          6\nLLM calls avoided:       5\nEst. latency saved:      1500.0ms\nEst. cost saved:         $0.1688\nActual execution time:   4.2ms\nPriced against:          anthropic/claude-opus-4-7 (as of 2026-05-01)\n```\n\nEvery report built from the table carries the snapshot's `as_of` date so\nstale prices are visible. Unknown `(provider, model)` pairs raise\n`CostProfileError` rather than guessing. Pass an explicit\n`profile=CostProfile(...)` when you have better per-call numbers, or set\n`cost_profile=` on `FlowExecutor` to attach a report to every\n`ExecutionResult`. Prices are refreshed by a maintainer-reviewed PR\n(`.github/workflows/update-prices.yml`) — never auto-merged.\n\nThese reports are **estimates** unless their inputs come from observed trace\nmeasurements. They must not be presented as evidence that a candidate should be\ncompiled; #377 tracks calibration of assumed versus measured model mediation.\n\n---\n\n## Export adapters\n\nHand a compiled flow off to any external agent framework via\n`chainweaver.export`:\n\n```python\nfrom chainweaver.export import (\n    flow_to_anthropic_tool,\n    flow_to_callable,\n    flow_to_openai_function,\n)\n\nopenai_spec = flow_to_openai_function(flow, executor)\nanthropic_spec = flow_to_anthropic_tool(flow, executor)\nrun = flow_to_callable(flow, executor)  # plain dict → dict callable\n```\n\n`flow_to_openai_function` emits the\n`{\"type\": \"function\", \"function\": {…}}` shape OpenAI's chat / responses\nAPIs expect.  `flow_to_anthropic_tool` emits Anthropic's `tool_use`\nshape.  `flow_to_callable` wraps the flow as a `Callable[[dict], dict]`\nsuitable for any framework that accepts arbitrary Python callables.\n\nNone of these adapters imports `openai` or `anthropic` — they emit\ndicts and callables only.  Runtime integration with those clients is\nthe caller's job.\n\nRunnable example: [`examples/export_openai_anthropic.py`](examples/export_openai_anthropic.py).\n\n---\n\n## Ecosystem bridges (LangChain, LlamaIndex)\n\n`chainweaver.integrations.langchain` and\n`chainweaver.integrations.llamaindex` ship thin bidirectional adapters\nso existing LangChain `BaseTool` / LlamaIndex `FunctionTool`\ninstances can be pulled into ChainWeaver, and ChainWeaver `Tool`\ninstances can be pushed back out.\n\n```python\nfrom chainweaver.integrations.langchain import (\n    from_langchain_tool,\n    to_langchain_tool,\n)\n\ncw_tool = from_langchain_tool(my_langchain_tool)\nlc_tool = to_langchain_tool(my_cw_tool)\n```\n\nInstall with `pip install 'chainweaver[langchain]'` /\n`'chainweaver[llamaindex]'`.  Importing either module without the\nrelevant extra raises a clear `ImportError`.\n\n---\n\n## Plugin discovery\n\nFor third-party packages — `chainweaver-aws`, `chainweaver-stripe`,\n… — ChainWeaver follows the same entry-point convention used by\npytest, Sphinx, MkDocs, and friends.\n\nPublisher (`pyproject.toml`):\n\n```toml\n[project.entry-points.\"chainweaver.tools\"]\naws = \"chainweaver_aws:get_tools\"\n\n[project.entry-points.\"chainweaver.flows\"]\naws = \"chainweaver_aws:get_flows\"\n```\n\nConsumer:\n\n```python\nfrom chainweaver import FlowExecutor, FlowRegistry\n\n# Auto-register every tool / flow advertised by an installed plugin.\nregistry = FlowRegistry(discover_plugins=True)\nexecutor = FlowExecutor(registry=registry, discover_plugins=True)\n```\n\nDiscovery is **opt-in** — importing `chainweaver` does not trigger\nplugin imports.  Misbehaving plugins (raise on import, return the\nwrong type) are logged at `WARNING` and skipped; pass\n`strict=True` to `discover_tools()` / `discover_flows()` for the loud\nform.\n\nRunnable example: [`examples/plugin_discovery.py`](examples/plugin_discovery.py).\n\n---\n\n## Runtime learning\n\nChainWeaver can watch what an agent does and **propose** deterministic-flow\ncandidates for repeated paths. A repeated sequence is not proof that the path\nis safe or valuable to compile; proposals require review, and the product\nvalidation program is explicitly measuring false positives, false negatives,\nand useful rejections.\n\n```python\nfrom chainweaver import ChainObserver, FlowRegistry\n\nobserver = ChainObserver()\n\n# Record tool calls as the agent makes them.\nobserver.record(\"fetch\", {\"url\": \"...\"}, {\"body\": \"...\"})\nobserver.record(\"validate\", {\"body\": \"...\"}, {\"valid\": True})\nobserver.record(\"transform\", {\"body\": \"...\"}, {\"records\": [1, 2, 3]})\nobserver.end_trace()\n# ... many traces later ...\n\nregistry = FlowRegistry()\nfor suggestion in observer.suggest_flows(min_occurrences=3):\n    # Suggestions are proposals — review; never treat confidence as authorization.\n    print(suggestion.flow.name, suggestion.confidence,\n          suggestion.estimated_llm_calls_avoided)\n    registry.register_flow(suggestion.flow)\n```\n\n- **`ChainObserver`** (#78) mines repeated tool sequences from runtime traces and\n  emits ranked `FlowSuggestion`s — never auto-registered.\n- **`chainweaver record`** (#226) mines recorded JSONL traces and writes candidate\n  flow files for explicit review/promotion.\n- **`ChainWeaverService`** (#101) ties observation, static analysis, and optional\n  offline proposals into an *analyze → propose → govern → promote* loop.\n\nSee [Product validation & adoption gates](docs/product-validation.md) before\ninterpreting a suggestion as proof that a path should become deterministic.\n\n---\n\n## Roadmap\n\nThe current roadmap is **validation-first**, not feature-count-first. The latest\npublished release is `v0.14.1`; newer work on `main` remains unreleased until a\nsubsequent release is cut.\n\n| Priority | Work | Why |\n|---|---|---|\n| **P0** | [#553 independent product falsification](https://github.com/dgenio/ChainWeaver/issues/553) | Establish whether trace-derived discovery/governance beats human inspection + a plain-function baseline. |\n| **P0** | [#554 authorization/approval preservation](https://github.com/dgenio/ChainWeaver/issues/554) | Compilation may remove reasoning boundaries, never silently security boundaries. |\n| **P0** | [#522 stable/supported/experimental API tiers](https://github.com/dgenio/ChainWeaver/issues/522) | Keep the compatibility promise smaller than the implementation surface. |\n| **P0** | [#519 release coherence](https://github.com/dgenio/ChainWeaver/issues/519) | Source, package, tag, release, docs, and artifacts must agree. |\n| **P1** | [#527 privacy profiles](https://github.com/dgenio/ChainWeaver/issues/527) | Trace analysis must work with minimized/local evidence. |\n| **Gate on #553** | [#334 canonical evidence architecture](https://github.com/dgenio/ChainWeaver/issues/334) | Build the large lifecycle model only after users validate the job. |\n| **Gate on #553** | [#498 production golden path](https://github.com/dgenio/ChainWeaver/issues/498) | Turn validated needs into one canonical end-to-end proof. |\n| **Explore if demanded** | [#555 portable compiled capabilities](https://github.com/dgenio/ChainWeaver/issues/555) | If users value analysis but not `FlowExecutor`, make the accepted artifact portable. |\n| **Before broad distribution** | [#556 naming/search decision](https://github.com/dgenio/ChainWeaver/issues/556) | Resolve discoverability/ambiguity while migration is still cheap. |\n\nBroad directory submissions, hosted-playground investment, and additional\nframework-adapter breadth are deliberately lower priority until these gates\nproduce evidence.\n\n`v1.0.0` is also evidence-gated: independent workloads/adopters, a manual\nbaseline, an external security review, repeated use, a downstream integration,\nand a 30-day RC compatibility soak are required by\n[docs/v1-release-criteria.md](docs/v1-release-criteria.md).\n\n---\n\n## Command-line interface\n\nChainWeaver ships a `chainweaver` console script with the following subcommands.\nReading `.flow.yaml` files needs the YAML extra\n(`pip install 'chainweaver[yaml]'` — also listed in [Installation](#installation)).\nThe `run` example below uses a flow shipped under `examples/`, so it should be\ninvoked from the repository root.\n\n```bash\n# Run a flow from disk — no Python required.\nchainweaver run examples/double_add_format.flow.yaml \\\n    --tools examples.simple_linear_flow \\\n    --input '{\"number\": 5}'\n\n# Serve a flow as MCP tools (needs chainweaver[mcp]) — agents call the whole\n# compiled flow as one deterministic tool. See docs/mcp-server.md.\nchainweaver serve examples/double_add_format.flow.yaml \\\n    --tools examples.simple_linear_flow\n\n# Validate a flow file (used by CI gates and editor tooling).\nchainweaver validate flows/etl.flow.yaml\nchainweaver check flows/                  # whole-directory variant\n\n# Scaffold a runnable first flow project (tools + flow file + run script).\nchainweaver init my-first-flow --template linear --with-tests\n\n# Render a flow as ASCII, Graphviz DOT, or Mermaid. Discover it from a directory\n# of flow files, an installed package's entry points, or the default registry.\nchainweaver viz my_flow --discover-dir flows/ --format dot | dot -Tpng -o my_flow.png\nchainweaver viz my_flow --discover-dir flows/ --format mermaid\nchainweaver viz --result trace.json --format mermaid   # overlay a real run\n\n# Explain a flow deterministically (LLM-free) for review — paste into a PR.\nchainweaver explain my_flow --discover-dir flows/ > flow-review.md\n\n# Inspect a flow's structure (table or JSON). `flows list` previews what is\n# discoverable so you can see what `inspect`/`viz` can target.\nchainweaver inspect my_flow --discover-dir flows/ --format json\nchainweaver flows list --discover-dir flows/\n\n# Check that your environment is ready before running anything.\nchainweaver doctor flow --profile first-run\n\n# Inspect a coding-agent workspace's MCP / observe setup (read-only).\nchainweaver doctor vscode --workspace .\n\n# Install tab-completion for your shell (bash/zsh/fish).\nchainweaver --install-completion\n\n# Analyze ExecutionResult traces — bottlenecks, p50/p95/p99 across runs,\n# and per-step / per-tool retry / skip / fallback / failure aggregates.\nchainweaver profile trace_a.json trace_b.json --format json\n\n# Compare two ExecutionResult JSON files step-by-step.\nchainweaver diff baseline.json current.json --perf-tolerance 25\n\n# Observed-determinism attestation: run N inputs × M repeats.\nchainweaver attest flows/etl.flow.yaml --tools my_pkg.tools --runs 50 --repeats 3\n\n# Advisory optimization suggestions for a saved flow.\nchainweaver suggest flows/etl.flow.yaml --tools my_pkg.tools --trace trace_a.json\n\n# Mine candidate flows from a recorded JSONL tool trace (offline, no LLM).\nchainweaver record examples/agent_tool_trace.jsonl --output-dir candidates/\nchainweaver flows promote candidates/suggested__fetch__validate.flow.yaml --to reviewed\nchainweaver flows promote candidates/suggested__fetch__validate.flow.yaml --to active\n\n# Run one continuous-analysis service pass and report flow proposals.\nchainweaver service --tools my_pkg.tools --trace trace.jsonl\n\n# Check saved flows for tool schema drift against the live registry.\nchainweaver doctor flow flows/ --check-drift --tools my_pkg.tools\n\n# Property-based fuzzing: generate cases, check invariants, save/minimize failures.\nchainweaver fuzz flows/etl.flow.yaml --tools my_pkg.tools \\\n  --property my_pkg.props:no_unauthorized_action --runs 1000 --seed 42 \\\n  --minimize --save-failures failures/\n```\n\n`run` is the fastest path from a fresh install to seeing a flow execute:\npoint it at a `.flow.yaml`/`.flow.json` file, pass `--tools <module>` (the\nimport path of a Python module that exposes `Tool` instances at top\nlevel), and supply the initial input as JSON. Hand-authored flow files must\ndeclare a `type: Flow` (or `type: DAGFlow`) discriminator at the top — see\nthe [flow file format](docs/cli.md#flow-file-format) reference. Most\nreporting subcommands also accept `--format json` for machine consumption\n(`inspect`, `validate`, `check`, `run`, `profile`, `diff`, `attest`,\n`suggest`, `doctor`); the exceptions are `viz`, which uses\n`--format ascii|dot|mermaid`, `explain`, which uses `--format md|text`, and\n`dump-schema`, which writes a raw JSON Schema and has no `--format` flag. The result-producing commands (`inspect`,\n`validate`, `check`, `profile`, `diff`, `attest`) wrap their `--format json`\noutput in a stable, versioned envelope\n(`{\"schema_version\", \"status\", \"data\", \"errors\"}`) so automation can branch on\n`status` / error codes — see\n[machine-readable output](docs/cli.md#machine-readable-output---format-json).\nAll subcommands share the same exit-code contract (`0` success, `1`\nbusiness-logic error, `2` file-not-found / argument error), and the CLI ships\ntab-completion (`chainweaver --install-completion`).\n\n**`inspect` and `viz` resolve flows from disk or a registry.**\nPass `--file <path>`, `--discover-dir <dir>`, or `--discover-entry-points` to\nresolve a flow without writing any Python (issue #381); `chainweaver flows\nlist` previews what is discoverable. With no discovery flag they fall back to a\nprocess-scoped, in-memory registry installed programmatically — running\n`chainweaver inspect my_flow` with neither a flag nor a configured registry\nexits `1` with `No registry configured. Call\nchainweaver.cli.set_default_registry(...) before invoking the CLI.`. To wire\nthe default-registry path, use a small entry script:\n\n```python\n# my_cli_entry.py\nfrom chainweaver import FlowRegistry\nfrom chainweaver.cli import main, set_default_registry\nfrom my_app import build_registry  # returns a populated FlowRegistry\n\nset_default_registry(build_registry())\nmain()\n```\n\nSee [`docs/cli.md` § Programmatic registration](docs/cli.md#programmatic-registration-inspect-viz)\nfor the full pattern, including why the split exists (file-oriented\ncommands stay zero-config, registry-oriented commands stay\nintrospection-friendly).\n\n---\n\n## Development\n\nNew contributors: see [**Your first contribution**](CONTRIBUTING.md#your-first-contribution)\nin `CONTRIBUTING.md` for the `good-first-issue` / `good-first-ai-issue` onramp\nand the step-by-step path to your first PR.\n\n```bash\n# Install with the integration extras and the maintainer tooling group\npip install --upgrade pip          # --group needs pip >= 25.1\npip install -e \".[integrations]\" --group dev\n\n# Run tests\npython -m pytest tests/ -v\n\n# Run the examples\npython examples/simple_linear_flow.py   # simple arithmetic flow\npython examples/etl_flow.py             # ETL flow\npython examples/mcp_search_flow.py      # MCP-style search & summarize flow\npython examples/naive_vs_compiled.py    # naive vs compiled timing comparison\npython examples/coding_agent_pr_review.py\npython examples/coding_agent_changelog.py\npython examples/coding_agent_debug_log.py\n```\n\n---\n\n## License\n\nThis project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.\n",
  "bytes": 53610,
  "sha": "4ab64171407c440abf85ecba2708406732b9afc8c7bebbd84c1d41fe4a2cd5ab",
  "repo_slug": "dgenio/chainweaver",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_dgenio_chainweaver_8a42581e/readme"
}