From 665c10ff1980482929358199a21fc504108066f7 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Fri, 3 Jul 2026 01:13:52 +0800 Subject: [PATCH 01/14] docs(graphs): add generated documentation atlas --- README.md | 2 +- docs/architecture.md | 4 +- docs/development.md | 4 +- docs/graphs/README.md | 26 + docs/graphs/agent-lifecycle.md | 41 + docs/graphs/app-composition.md | 90 ++ docs/graphs/capability-seams.md | 110 +++ docs/graphs/event-producer-consumer.md | 36 + docs/graphs/hot-reload-disposal.md | 24 + docs/graphs/package-topology.md | 189 ++++ docs/graphs/session-surface.md | 25 + docs/graphs/snapshot-replay.md | 24 + docs/graphs/subagent-lineage.md | 27 + docs/graphs/tool-affordance-map.md | 35 + docs/graphs/tool-execution-pipeline.md | 30 + docs/rfc/README.md | 1 + .../2026-07-03-documentation-graph-atlas.md | 66 ++ package.json | 4 +- scripts/gen-doc-graphs.ts | 851 ++++++++++++++++++ 19 files changed, 1584 insertions(+), 5 deletions(-) create mode 100644 docs/graphs/README.md create mode 100644 docs/graphs/agent-lifecycle.md create mode 100644 docs/graphs/app-composition.md create mode 100644 docs/graphs/capability-seams.md create mode 100644 docs/graphs/event-producer-consumer.md create mode 100644 docs/graphs/hot-reload-disposal.md create mode 100644 docs/graphs/package-topology.md create mode 100644 docs/graphs/session-surface.md create mode 100644 docs/graphs/snapshot-replay.md create mode 100644 docs/graphs/subagent-lineage.md create mode 100644 docs/graphs/tool-affordance-map.md create mode 100644 docs/graphs/tool-execution-pipeline.md create mode 100644 docs/rfc/implemented/process/2026-07-03-documentation-graph-atlas.md create mode 100644 scripts/gen-doc-graphs.ts diff --git a/README.md b/README.md index 1ce5aa8960..2d97f103af 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,6 @@ pnpm run demo:echo # runnable echo-agent example (no API key needed) pnpm run demo:coding # the real DeepSeek coding agent (needs DEEPSEEK_API_KEY) ``` -For humans, start with the [development guide](docs/development.md) for local setup, hooks, environment variables, and quality gates, then read the [architecture design](docs/architecture.md) before package work. Local context lives in [packages/](packages/) and [vendor/](vendor/). +For humans, start with the [development guide](docs/development.md) for local setup, hooks, environment variables, and quality gates, then read the [architecture design](docs/architecture.md) and [documentation graph atlas](docs/graphs/README.md) before package work. Local context lives in [packages/](packages/) and [vendor/](vendor/). For agents, follow [AGENTS.md](AGENTS.md). diff --git a/docs/architecture.md b/docs/architecture.md index 5cd56c6e91..903e58909c 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -8,9 +8,9 @@ The harness core is deliberately tiny: a handful of abstract services plus one c Requirement context: [Coding Harness MVP 需求分析][mvp-doc]. -For a catalog of the **data structures** this architecture moves around — the core vocabulary types, their literal shapes, and the seam types grouped by capability — see [core-data-structures/](core-data-structures/core.md). This document covers behavior; that one covers the types. +For a catalog of the **data structures** this architecture moves around — the core vocabulary types, their literal shapes, and the seam types grouped by capability — see [core-data-structures/](core-data-structures/core.md). For visual relationship maps across packages, seams, events, tools, lifecycle, and replay, see the [documentation graph atlas](graphs/README.md). This document covers behavior; those references cover types and topology. -**Contents:** [Layering](#layering) · [Service map](#service-map) · [Capability seams](#capability-seams-interface--implementation--consumer) · [The vocabulary (dsh-llm)](#the-vocabulary-dsh-llm) · [Event-sourced sessions](#event-sourced-sessions-dsh-session) · [Prompt assembly](#prompt-assembly-dsh-system-prompt) · [Tool pipeline](#tool-pipeline-dsh-tools) · [Agents and the loop](#agents-dsh-agent-and-the-loop-dsh-agent-loop) ([lifecycle](#loop-lifecycle-session--turn--step), [event taxonomy](#event-taxonomy), [waterfall semantics](#cordis-waterfall-semantics-important)) · [Plugin sanity checklist](#plugin-sanity-checklist) · [Extension cookbook](#extension-cookbook) · [Deferred work](#deferred-work-todo) +**Contents:** [Layering](#layering) · [Service map](#service-map) · [Capability seams](#capability-seams-interface--implementation--consumer) · [The vocabulary (dsh-llm)](#the-vocabulary-dsh-llm) · [Event-sourced sessions](#event-sourced-sessions-dsh-session) · [Prompt assembly](#prompt-assembly-dsh-system-prompt) · [Tool pipeline](#tool-pipeline-dsh-tools) · [Agents and the loop](#agents-dsh-agent-and-the-loop-dsh-agent-loop) ([lifecycle](#loop-lifecycle-session--turn--step), [event taxonomy](#event-taxonomy), [waterfall semantics](#cordis-waterfall-semantics-important)) · [Graph atlas](graphs/README.md) · [Plugin sanity checklist](#plugin-sanity-checklist) · [Extension cookbook](#extension-cookbook) · [Deferred work](#deferred-work-todo) [microkernel-doc]: https://trtgsjkv6r.feishu.cn/wiki/VS9Lw1kQki6mDJk2UHocyuphnsc [mvp-doc]: https://trtgsjkv6r.feishu.cn/wiki/ZwK6wfBE9i91V6kzMGYcgRGanxg diff --git a/docs/development.md b/docs/development.md index 431d7b4dac..965f23d90f 100644 --- a/docs/development.md +++ b/docs/development.md @@ -96,9 +96,11 @@ pnpm run lint:fix # eslint . --fix pnpm run doc-typecheck # compile checked TypeScript snippets in Markdown docs pnpm run gen-cordis-catalog # regenerate docs/cordis-catalog/events-and-services.md from source pnpm run verify-cordis-catalog # fail if the cordis events/services catalog is stale +pnpm run gen-doc-graphs # regenerate docs/graphs/*.md from source and curated graph definitions +pnpm run verify-doc-graphs # fail if docs/graphs/*.md is stale pnpm run verify-md-wrap # fail on hard-wrapped prose paragraphs in docs/README markdown pnpm run verify-type-equiv # fail if a ```ts type-equiv doc block drifts from its source type -pnpm run doc-sync # doc-typecheck, cordis-catalog freshness, markdown wrap/link, and type-equiv verification +pnpm run doc-sync # doc-typecheck, generated doc freshness, markdown wrap/link, and type-equiv verification pnpm run gen-module-graph # regenerate docs/module-graph.md from package peerDeps pnpm run verify-module-graph # fail if docs/module-graph.md is stale pnpm run build # emit lib/types intermediates, then bundle lib/index.* runtime files diff --git a/docs/graphs/README.md b/docs/graphs/README.md new file mode 100644 index 0000000000..995481d160 --- /dev/null +++ b/docs/graphs/README.md @@ -0,0 +1,26 @@ + + +# Documentation Graph Atlas + +Maintenance mode: mixed: each linked page declares generated, hybrid, or curated mode. + +The graph atlas is the relationship layer above the generated catalogs. Use it to navigate package topology, capability seams, event flow, model-facing tools, and runtime lifecycle paths. Exact signatures and type shapes still live in [cordis-catalog/](../cordis-catalog/events-and-services.md), [tool-catalog/](../tool-catalog/tools.md), and [core-data-structures/](../core-data-structures/core.md). + +The process decision behind this atlas is recorded in [the documentation graph atlas RFC](../rfc/implemented/process/2026-07-03-documentation-graph-atlas.md). + +| Graph | Mode | +| --- | --- | +| [package topology by group](package-topology.md) | `generated` | +| [capability seams and core services](capability-seams.md) | `hybrid generated` | +| [app composition](app-composition.md) | `hybrid generated` | +| [event producer/consumer matrix](event-producer-consumer.md) | `hybrid generated` | +| [tool affordance map](tool-affordance-map.md) | `hybrid generated` | +| [agent turn and step lifecycle](agent-lifecycle.md) | `curated` | +| [tool execution pipeline](tool-execution-pipeline.md) | `curated` | +| [session surface and message projection](session-surface.md) | `curated` | +| [subagent and session lineage](subagent-lineage.md) | `curated` | +| [plugin disposal and hot reload ownership](hot-reload-disposal.md) | `curated` | +| [ACP snapshot replay](snapshot-replay.md) | `curated` | + +Regenerate with `pnpm run gen-doc-graphs`; verify freshness with `pnpm run verify-doc-graphs`. diff --git a/docs/graphs/agent-lifecycle.md b/docs/graphs/agent-lifecycle.md new file mode 100644 index 0000000000..cc25e25694 --- /dev/null +++ b/docs/graphs/agent-lifecycle.md @@ -0,0 +1,41 @@ + + +# Agent Turn And Step Lifecycle + +Maintenance mode: curated Mermaid sequence; exact event signatures live in the generated Cordis catalog. + +This sequence is the visual companion to [architecture.md](../architecture.md#loop-lifecycle-session--turn--step). It shows the durable session event path separately from live `agent/*` notifications. + +```mermaid +sequenceDiagram + participant User + participant Agent + participant Loop + participant Prompt as ctx.systemPrompt + participant LLM as ctx.llm + participant Tools as ctx.tools + participant Session + participant Persistence + User->>Agent: send(content) + Agent->>Loop: queued work wakes driver + Loop->>Session: turn/start + user/message + Loop-->>User: agent/turn-start + Loop->>Prompt: system-prompt/assemble waterfall + Loop-->>Loop: agent/pre-step serial checkpoint + Loop->>Session: step/start + Loop->>LLM: agent/request waterfall, then llm/stream waterfall + LLM-->>Loop: StreamChunk* + Loop->>Session: assistant/chunk* + Loop-->>User: agent/stream-chunk* (master live mirror) + Loop->>Session: assistant/message + Loop->>Tools: tools/execute waterfall for each tool-call + Tools-->>Session: tool-owned events when applicable + Loop->>Session: tool/result + Loop-->>Loop: agent/turn-continuation waterfall + Loop->>Session: turn/end + Loop->>Persistence: session/flush parallel checkpoint + Loop-->>User: agent/status idle +``` + +Future pressure from the hooks stack: PR #129 removes the live `agent/stream-chunk` mirror and leaves durable `assistant/chunk` on `session/event` as the authoritative token stream. Consumers that need replayable transcript data should already treat `session/event` as the load-bearing path. diff --git a/docs/graphs/app-composition.md b/docs/graphs/app-composition.md new file mode 100644 index 0000000000..ce2bab32d8 --- /dev/null +++ b/docs/graphs/app-composition.md @@ -0,0 +1,90 @@ + + +# App Composition + +Maintenance mode: hybrid: leaf plugin lists are parsed from `examples/*/cordis.yml`; bundle expansions are curated from app package source. + +This graph is for SDK users asking which pieces a runnable agent loads. Leaf configs choose adapters and optional product tools; app packages provide the front door; `dsh-agent-core` bundles the providerless spine. + +```mermaid +flowchart LR + subgraph example_echo["examples/echo-agent"] + cfg_echo["cordis.yml"] + plugin_echo_hmr["hmr
@cordisjs/plugin-hmr"] + cfg_echo --> plugin_echo_hmr + plugin_echo_mock_llm["mock-llm
./src/mock-llm.ts"] + cfg_echo --> plugin_echo_mock_llm + plugin_echo_echo_tool["echo-tool
./src/echo-tool.ts"] + cfg_echo --> plugin_echo_echo_tool + plugin_echo_bash["bash
@deepseek-ai/dsh-bash-local"] + cfg_echo --> plugin_echo_bash + plugin_echo_stdio_agent["stdio-agent
@deepseek-ai/dsh-stdio-agent"] + cfg_echo --> plugin_echo_stdio_agent + plugin_echo_stdio_agent --> bundle_stdio + end + subgraph example_coding["examples/coding-agent"] + cfg_coding["cordis.yml"] + plugin_coding_hmr["hmr
@cordisjs/plugin-hmr"] + cfg_coding --> plugin_coding_hmr + plugin_coding_llm_deepseek["llm-deepseek
@deepseek-ai/dsh-llm-deepseek"] + cfg_coding --> plugin_coding_llm_deepseek + plugin_coding_bash["bash
@deepseek-ai/dsh-bash-local"] + cfg_coding --> plugin_coding_bash + plugin_coding_stdio_agent["stdio-agent
@deepseek-ai/dsh-stdio-agent"] + cfg_coding --> plugin_coding_stdio_agent + plugin_coding_stdio_agent --> bundle_stdio + plugin_coding_compact_basic["compact-basic
@deepseek-ai/dsh-compact-basic"] + cfg_coding --> plugin_coding_compact_basic + plugin_coding_subagent["subagent
@deepseek-ai/dsh-subagent"] + cfg_coding --> plugin_coding_subagent + plugin_coding_subagent_spawn["subagent-spawn
@deepseek-ai/dsh-subagent-spawn"] + cfg_coding --> plugin_coding_subagent_spawn + plugin_coding_subagent_fork["subagent-fork
@deepseek-ai/dsh-subagent-fork"] + cfg_coding --> plugin_coding_subagent_fork + plugin_coding_tool_subagent["tool-subagent
@deepseek-ai/dsh-tool-subagent"] + cfg_coding --> plugin_coding_tool_subagent + plugin_coding_tool_subagent_fork["tool-subagent-fork
@deepseek-ai/dsh-tool-subagent"] + cfg_coding --> plugin_coding_tool_subagent_fork + plugin_coding_tool_todo["tool-todo
@deepseek-ai/dsh-tool-todo"] + cfg_coding --> plugin_coding_tool_todo + end + subgraph example_acp["examples/acp-agent"] + cfg_acp["cordis.yml"] + plugin_acp_llm_deepseek["llm-deepseek
@deepseek-ai/dsh-llm-deepseek"] + cfg_acp --> plugin_acp_llm_deepseek + plugin_acp_bash["bash
@deepseek-ai/dsh-bash-local"] + cfg_acp --> plugin_acp_bash + plugin_acp_acp_agent["acp-agent
@deepseek-ai/dsh-acp-agent"] + cfg_acp --> plugin_acp_acp_agent + plugin_acp_acp_agent --> bundle_acp_agent + plugin_acp_subagent["subagent
@deepseek-ai/dsh-subagent"] + cfg_acp --> plugin_acp_subagent + plugin_acp_subagent_spawn["subagent-spawn
@deepseek-ai/dsh-subagent-spawn"] + cfg_acp --> plugin_acp_subagent_spawn + plugin_acp_subagent_fork["subagent-fork
@deepseek-ai/dsh-subagent-fork"] + cfg_acp --> plugin_acp_subagent_fork + plugin_acp_tool_subagent["tool-subagent
@deepseek-ai/dsh-tool-subagent"] + cfg_acp --> plugin_acp_tool_subagent + plugin_acp_tool_subagent_fork["tool-subagent-fork
@deepseek-ai/dsh-tool-subagent"] + cfg_acp --> plugin_acp_tool_subagent_fork + plugin_acp_tool_todo["tool-todo
@deepseek-ai/dsh-tool-todo"] + cfg_acp --> plugin_acp_tool_todo + end + bundle_stdio["@deepseek-ai/dsh-stdio-agent"] --> bundle_agent_core["@deepseek-ai/dsh-agent-core"] + bundle_stdio --> bundle_jsonl["@deepseek-ai/dsh-session-persistence-jsonl"] + bundle_stdio --> bundle_ui_stdio["@deepseek-ai/dsh-ui-stdio"] + bundle_acp_agent["@deepseek-ai/dsh-acp-agent"] --> bundle_agent_core + bundle_acp_agent --> bundle_jsonl + bundle_acp_agent --> bundle_acp["@deepseek-ai/dsh-acp"] + bundle_agent_core --> spine_llm["ctx.llm"] + bundle_agent_core --> spine_sessions["ctx.sessions"] + bundle_agent_core --> spine_tools["ctx.tools + tool-bash"] + bundle_agent_core --> spine_loop["ctx.agents + ctx.agentLoop"] +``` + +| Example | Parsed plugin ids | Config | +| --- | --- | --- | +| `examples/echo-agent` | `hmr`, `mock-llm`, `echo-tool`, `bash`, `stdio-agent` | [`examples/echo-agent/cordis.yml`](../../examples/echo-agent/cordis.yml) | +| `examples/coding-agent` | `hmr`, `llm-deepseek`, `bash`, `stdio-agent`, `compact-basic`, `subagent`, `subagent-spawn`, `subagent-fork`, `tool-subagent`, `tool-subagent-fork`, `tool-todo` | [`examples/coding-agent/cordis.yml`](../../examples/coding-agent/cordis.yml) | +| `examples/acp-agent` | `llm-deepseek`, `bash`, `acp-agent`, `subagent`, `subagent-spawn`, `subagent-fork`, `tool-subagent`, `tool-subagent-fork`, `tool-todo` | [`examples/acp-agent/cordis.yml`](../../examples/acp-agent/cordis.yml) | diff --git a/docs/graphs/capability-seams.md b/docs/graphs/capability-seams.md new file mode 100644 index 0000000000..d3b95b55f8 --- /dev/null +++ b/docs/graphs/capability-seams.md @@ -0,0 +1,110 @@ + + +# Capability Seams And Core Services + +Maintenance mode: hybrid: services are discovered from Cordis declarations; interface/implementation/consumer roles are classified in `scripts/gen-doc-graphs.ts` with a completeness guard. + +A service can be a core spine service, a swappable capability seam, or a bundle/composition point. The graph shows the package that owns the service declaration, known implementation packages, and packages that consume the service directly. + +```mermaid +flowchart LR + pkg_llm["llm"] + svc_llm["ctx.llm
LLM adapter registry"] + pkg_llm_deepseek["llm-deepseek"] + pkg_llm_pi_ai["llm-pi-ai"] + pkg_llm_replay["llm-replay"] + pkg_agent_loop["agent-loop"] + pkg_compact_basic["compact-basic"] + pkg_session["session"] + svc_sessions["ctx.sessions
In-memory session store"] + pkg_agent["agent"] + pkg_session_persistence["session-persistence"] + pkg_subagent_inprocess["subagent-inprocess"] + pkg_invariants["invariants"] + svc_sessionPersistence["ctx.sessionPersistence
Durable session persistence seam"] + pkg_session_persistence_jsonl["session-persistence-jsonl"] + pkg_session_persistence_sqlite["session-persistence-sqlite"] + pkg_acp["acp"] + pkg_system_prompt["system-prompt"] + svc_systemPrompt["ctx.systemPrompt
System prompt assembly registry"] + pkg_tools["tools"] + svc_tools["ctx.tools
Tool registry and execution waterfall"] + pkg_tool_bash["tool-bash"] + pkg_tool_subagent["tool-subagent"] + pkg_tool_todo["tool-todo"] + svc_agents["ctx.agents
Agent registry"] + pkg_stdio_agent["stdio-agent"] + svc_agentLoop["ctx.agentLoop
Concrete loop driver"] + pkg_agent_core["agent-core"] + pkg_bash["bash"] + svc_bash["ctx.bash
Bash executor seam"] + pkg_bash_local["bash-local"] + pkg_compact["compact"] + svc_compact["ctx.compact
Compaction seam"] + pkg_subagent["subagent"] + svc_subagents["ctx.subagents
Subagent provider registry"] + pkg_subagent_spawn["subagent-spawn"] + pkg_subagent_fork["subagent-fork"] + pkg_subagent_acp["subagent-acp"] + pkg_subagent_mock["subagent-mock"] + pkg_agent --> svc_agents + pkg_agent_loop --> svc_agentLoop + pkg_bash --> svc_bash + pkg_bash_local --> svc_bash + pkg_compact --> svc_compact + pkg_compact_basic --> svc_compact + pkg_llm --> svc_llm + pkg_llm_deepseek --> svc_llm + pkg_llm_pi_ai --> svc_llm + pkg_llm_replay --> svc_llm + pkg_session --> svc_sessions + pkg_session_persistence --> svc_sessionPersistence + pkg_session_persistence_jsonl --> svc_sessionPersistence + pkg_session_persistence_sqlite --> svc_sessionPersistence + pkg_subagent --> svc_subagents + pkg_subagent_acp --> svc_subagents + pkg_subagent_fork --> svc_subagents + pkg_subagent_mock --> svc_subagents + pkg_subagent_spawn --> svc_subagents + pkg_system_prompt --> svc_systemPrompt + pkg_tools --> svc_tools + svc_agentLoop --> pkg_agent_core + svc_agents --> pkg_acp + svc_agents --> pkg_agent_loop + svc_agents --> pkg_invariants + svc_agents --> pkg_stdio_agent + svc_agents --> pkg_subagent_inprocess + svc_bash --> pkg_tool_bash + svc_compact --> pkg_compact_basic + svc_llm --> pkg_agent_loop + svc_llm --> pkg_compact_basic + svc_sessionPersistence --> pkg_acp + svc_sessionPersistence --> pkg_agent_loop + svc_sessions --> pkg_agent + svc_sessions --> pkg_agent_loop + svc_sessions --> pkg_invariants + svc_sessions --> pkg_session_persistence + svc_sessions --> pkg_subagent_inprocess + svc_subagents --> pkg_tool_subagent + svc_systemPrompt --> pkg_agent_loop + svc_systemPrompt --> pkg_tools + svc_tools --> pkg_acp + svc_tools --> pkg_agent_loop + svc_tools --> pkg_tool_bash + svc_tools --> pkg_tool_subagent + svc_tools --> pkg_tool_todo +``` + +| ctx key | Role | Owner | Implementations | Direct consumers | Note | +| --- | --- | --- | --- | --- | --- | +| `ctx.llm` | `seam` | [`llm`](../../packages/llm/llm) | [`llm-deepseek`](../../packages/llm/llm-deepseek), [`llm-pi-ai`](../../packages/llm/llm-pi-ai), [`llm-replay`](../../packages/support/llm-replay) | [`agent-loop`](../../packages/core/agent-loop), [`compact-basic`](../../packages/compact/compact-basic) | Adapters register provider implementations; the loop and compaction call the provider-neutral stream service. | +| `ctx.sessions` | `core` | [`session`](../../packages/core/session) | - | [`agent-loop`](../../packages/core/agent-loop), [`agent`](../../packages/core/agent), [`session-persistence`](../../packages/session-persistence/session-persistence), [`subagent-inprocess`](../../packages/subagent/subagent-inprocess), [`invariants`](../../packages/support/invariants) | Owns append-only Session instances and emits the durable session event feed. | +| `ctx.sessionPersistence` | `seam` | [`session-persistence`](../../packages/session-persistence/session-persistence) | [`session-persistence-jsonl`](../../packages/session-persistence/session-persistence-jsonl), [`session-persistence-sqlite`](../../packages/session-persistence/session-persistence-sqlite) | [`agent-loop`](../../packages/core/agent-loop), [`acp`](../../packages/ui/acp) | Backends persist the same SessionEvent vocabulary; apps choose a backend at composition time. | +| `ctx.systemPrompt` | `core` | [`system-prompt`](../../packages/core/system-prompt) | - | [`agent-loop`](../../packages/core/agent-loop), [`tools`](../../packages/core/tools) | Collects prompt sections and model-facing tool schemas for each step. | +| `ctx.tools` | `core` | [`tools`](../../packages/core/tools) | - | [`agent-loop`](../../packages/core/agent-loop), [`tool-bash`](../../packages/bash/tool-bash), [`tool-subagent`](../../packages/subagent/tool-subagent), [`tool-todo`](../../packages/todo/tool-todo), [`acp`](../../packages/ui/acp) | Registers tool definitions, exposes schemas to the prompt, and routes calls through tools/execute. | +| `ctx.agents` | `core` | [`agent`](../../packages/core/agent) | - | [`agent-loop`](../../packages/core/agent-loop), [`acp`](../../packages/ui/acp), [`subagent-inprocess`](../../packages/subagent/subagent-inprocess), [`stdio-agent`](../../packages/ui/stdio-agent), [`invariants`](../../packages/support/invariants) | Owns live Agent handles and the create/resume factory seam. | +| `ctx.agentLoop` | `bundle` | [`agent-loop`](../../packages/core/agent-loop) | - | [`agent-core`](../../packages/core/agent-core) | The one concrete loop plugin; extension packages depend on dsh-agent events and services, not on this package. | +| `ctx.bash` | `seam` | [`bash`](../../packages/bash/bash) | [`bash-local`](../../packages/bash/bash-local) | [`tool-bash`](../../packages/bash/tool-bash) | The model-facing bash tools consume this seam; sandboxed or remote executors can replace bash-local. | +| `ctx.compact` | `seam` | [`compact`](../../packages/compact/compact) | [`compact-basic`](../../packages/compact/compact-basic) | [`compact-basic`](../../packages/compact/compact-basic) | The basic backend currently consumes the pre-step event directly; a model-facing compact tool remains deferred. | +| `ctx.subagents` | `seam` | [`subagent`](../../packages/subagent/subagent) | [`subagent-spawn`](../../packages/subagent/subagent-spawn), [`subagent-fork`](../../packages/subagent/subagent-fork), [`subagent-acp`](../../packages/subagent/subagent-acp), [`subagent-mock`](../../packages/support/subagent-mock) | [`tool-subagent`](../../packages/subagent/tool-subagent) | Providers implement transports; tool-subagent exposes one configured provider as a model-facing tool name. | diff --git a/docs/graphs/event-producer-consumer.md b/docs/graphs/event-producer-consumer.md new file mode 100644 index 0000000000..dfce3fe04a --- /dev/null +++ b/docs/graphs/event-producer-consumer.md @@ -0,0 +1,36 @@ + + +# Event Producer And Consumer Matrix + +Maintenance mode: hybrid generated: Cordis event declarations and most producer/listener edges are AST-scanned; dynamic dispatch sites are classified in `scripts/gen-doc-graphs.ts`. + +This matrix shows which packages dispatch each harness-owned event and which packages listen to it. It is intentionally a table rather than one large graph: events are many-to-many, and dense relation data is easier to review in rows. Dynamic dispatch overrides cover sites that deliberately bypass `ctx.emit`, such as subagent lifecycle containment. + +| Event | Mode | Declared in | Dispatchers | Listeners | +| --- | --- | --- | --- | --- | +| `agent/created` | `emit` | [`packages/core/agent/src/types.ts:137`](../../packages/core/agent/src/types.ts) | [`agent`](../../packages/core/agent) (`emit`) | - | +| `agent/disposed` | `emit` | [`packages/core/agent/src/types.ts:143`](../../packages/core/agent/src/types.ts) | [`agent`](../../packages/core/agent) (`emit`) | - | +| `agent/error` | `emit` | [`packages/core/agent/src/types.ts:254`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`emit`) | - | +| `agent/pre-step` | `serial` | [`packages/core/agent/src/types.ts:214`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`serial`) | [`compact-basic`](../../packages/compact/compact-basic) | +| `agent/queued` | `emit` | [`packages/core/agent/src/types.ts:156`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`emit`) | - | +| `agent/request` | `waterfall` | [`packages/core/agent/src/types.ts:223`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`waterfall`), [`compact-basic`](../../packages/compact/compact-basic) (`waterfall`) | - | +| `agent/status` | `emit` | [`packages/core/agent/src/types.ts:150`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`emit`) | [`acp`](../../packages/ui/acp), [`invariants`](../../packages/support/invariants), [`ui-stdio`](../../packages/support/ui-stdio) | +| `agent/steering` | `emit` | [`packages/core/agent/src/types.ts:248`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`emit`) | - | +| `agent/step-end` | `emit` | [`packages/core/agent/src/types.ts:180`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`emit`) | - | +| `agent/step-result` | `waterfall` | [`packages/core/agent/src/types.ts:229`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`waterfall`) | - | +| `agent/step-start` | `emit` | [`packages/core/agent/src/types.ts:175`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`emit`) | - | +| `agent/stream-chunk` | `emit` | [`packages/core/agent/src/types.ts:243`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`emit`) | [`ui-stdio`](../../packages/support/ui-stdio) | +| `agent/turn-continuation` | `waterfall` | [`packages/core/agent/src/types.ts:236`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`waterfall`) | - | +| `agent/turn-end` | `emit` | [`packages/core/agent/src/types.ts:169`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`emit`) | [`ui-stdio`](../../packages/support/ui-stdio) | +| `agent/turn-start` | `emit` | [`packages/core/agent/src/types.ts:163`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`emit`) | [`ui-stdio`](../../packages/support/ui-stdio) | +| `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:31`](../../packages/llm/llm/src/index.ts) | [`llm`](../../packages/llm/llm) (`waterfall`) | [`llm-replay`](../../packages/support/llm-replay) | +| `session/created` | `emit` | [`packages/core/session/src/index.ts:34`](../../packages/core/session/src/index.ts) | [`session`](../../packages/core/session) (`emit`) | [`invariants`](../../packages/support/invariants), [`session-persistence`](../../packages/session-persistence/session-persistence) | +| `session/event` | `emit` | [`packages/core/session/src/index.ts:40`](../../packages/core/session/src/index.ts) | [`session`](../../packages/core/session) (`emit`) | [`acp`](../../packages/ui/acp), [`invariants`](../../packages/support/invariants), [`session-persistence`](../../packages/session-persistence/session-persistence), [`ui-stdio`](../../packages/support/ui-stdio) | +| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:49`](../../packages/core/session/src/index.ts) | [`agent-loop`](../../packages/core/agent-loop) (`parallel`) | [`session-persistence`](../../packages/session-persistence/session-persistence) | +| `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:65`](../../packages/subagent/subagent/src/index.ts) | [`subagent`](../../packages/subagent/subagent) (`events.dispatch`) | - | +| `subagent/start` | `emit` | [`packages/subagent/subagent/src/index.ts:59`](../../packages/subagent/subagent/src/index.ts) | [`subagent`](../../packages/subagent/subagent) (`events.dispatch`) | - | +| `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:24`](../../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../../packages/core/system-prompt) (`waterfall`) | - | +| `system-prompt/change` | `emit` | [`packages/core/system-prompt/src/index.ts:30`](../../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../../packages/core/system-prompt) (`emit`) | - | +| `tools/change` | `emit` | [`packages/core/tools/src/index.ts:48`](../../packages/core/tools/src/index.ts) | [`tools`](../../packages/core/tools) (`emit`) | - | +| `tools/execute` | `waterfall` | [`packages/core/tools/src/index.ts:43`](../../packages/core/tools/src/index.ts) | [`tools`](../../packages/core/tools) (`waterfall`) | - | diff --git a/docs/graphs/hot-reload-disposal.md b/docs/graphs/hot-reload-disposal.md new file mode 100644 index 0000000000..ca98c25692 --- /dev/null +++ b/docs/graphs/hot-reload-disposal.md @@ -0,0 +1,24 @@ + + +# Plugin Disposal And Hot Reload Ownership + +Maintenance mode: curated Mermaid flow based on Cordis fiber/effect conventions. + +This graph is a maintainer checklist for plugin authors: registrations are effects, service injection gates activation, and owned handles must be disposed by their owner. + +```mermaid +flowchart TD + plugin["ctx.plugin(plugin) creates fiber"] + inject["static inject gates activation"] + service["ctx.provide / Service constructor"] + effects["ctx.effect registrations
events, tools, adapters, timers"] + reload["HMR / fiber.dispose()"] + disposers["Run disposers in owner fiber"] + quiescence["Owned AgentHandle.dispose()
or service teardown awaits quiescence"] + plugin --> inject --> service + inject --> effects + reload --> disposers --> quiescence +``` + +Hook bridges and SDK plugins increase the number of long-lived listeners, so this ownership graph should stay small and visible. diff --git a/docs/graphs/package-topology.md b/docs/graphs/package-topology.md new file mode 100644 index 0000000000..68d5a2442c --- /dev/null +++ b/docs/graphs/package-topology.md @@ -0,0 +1,189 @@ + + +# Package Topology By Group + +Maintenance mode: generated from `packages/*/*/package.json` peer dependencies plus package group paths. + +This graph complements [module-graph.md](../module-graph.md): it keeps the same canonical peer-dependency edge source, but clusters packages by the `packages//` hierarchy so layering and capability families are easier to scan. + +```mermaid +flowchart TD + subgraph group_util["packages/util"] + pkg_brand["brand"] + end + subgraph group_llm["packages/llm"] + pkg_llm["llm"] + pkg_llm_deepseek["llm-deepseek"] + pkg_llm_pi_ai["llm-pi-ai"] + end + subgraph group_core["packages/core"] + pkg_agent["agent"] + pkg_agent_core["agent-core"] + pkg_agent_loop["agent-loop"] + pkg_session["session"] + pkg_system_prompt["system-prompt"] + pkg_tools["tools"] + end + subgraph group_bash["packages/bash"] + pkg_bash["bash"] + pkg_bash_local["bash-local"] + pkg_tool_bash["tool-bash"] + end + subgraph group_compact["packages/compact"] + pkg_compact["compact"] + pkg_compact_basic["compact-basic"] + end + subgraph group_subagent["packages/subagent"] + pkg_subagent["subagent"] + pkg_subagent_acp["subagent-acp"] + pkg_subagent_fork["subagent-fork"] + pkg_subagent_inprocess["subagent-inprocess"] + pkg_subagent_spawn["subagent-spawn"] + pkg_tool_subagent["tool-subagent"] + end + subgraph group_session_persistence["packages/session-persistence"] + pkg_session_persistence["session-persistence"] + pkg_session_persistence_jsonl["session-persistence-jsonl"] + pkg_session_persistence_sqlite["session-persistence-sqlite"] + end + subgraph group_todo["packages/todo"] + pkg_tool_todo["tool-todo"] + end + subgraph group_support["packages/support"] + pkg_invariants["invariants"] + pkg_llm_replay["llm-replay"] + pkg_subagent_mock["subagent-mock"] + pkg_ui_stdio["ui-stdio"] + end + subgraph group_ui["packages/ui"] + pkg_acp["acp"] + pkg_acp_agent["acp-agent"] + pkg_stdio_agent["stdio-agent"] + end + pkg_llm --> pkg_brand + pkg_bash --> pkg_brand + pkg_llm_deepseek --> pkg_llm + pkg_llm_pi_ai --> pkg_llm + pkg_session --> pkg_brand + pkg_session --> pkg_llm + pkg_system_prompt --> pkg_llm + pkg_bash_local --> pkg_bash + pkg_agent --> pkg_brand + pkg_agent --> pkg_llm + pkg_agent --> pkg_session + pkg_compact --> pkg_llm + pkg_compact --> pkg_session + pkg_session_persistence --> pkg_session + pkg_llm_replay --> pkg_llm + pkg_llm_replay --> pkg_session + pkg_tools --> pkg_agent + pkg_tools --> pkg_llm + pkg_tools --> pkg_system_prompt + pkg_compact_basic --> pkg_agent + pkg_compact_basic --> pkg_compact + pkg_compact_basic --> pkg_llm + pkg_compact_basic --> pkg_session + pkg_session_persistence_jsonl --> pkg_session + pkg_session_persistence_jsonl --> pkg_session_persistence + pkg_session_persistence_sqlite --> pkg_session + pkg_session_persistence_sqlite --> pkg_session_persistence + pkg_invariants --> pkg_agent + pkg_invariants --> pkg_llm + pkg_invariants --> pkg_session + pkg_ui_stdio --> pkg_agent + pkg_ui_stdio --> pkg_llm + pkg_ui_stdio --> pkg_session + pkg_agent_loop --> pkg_agent + pkg_agent_loop --> pkg_llm + pkg_agent_loop --> pkg_session + pkg_agent_loop --> pkg_session_persistence + pkg_agent_loop --> pkg_system_prompt + pkg_agent_loop --> pkg_tools + pkg_tool_bash --> pkg_agent + pkg_tool_bash --> pkg_bash + pkg_tool_bash --> pkg_llm + pkg_tool_bash --> pkg_tools + pkg_subagent --> pkg_agent + pkg_subagent --> pkg_llm + pkg_subagent --> pkg_tools + pkg_tool_todo --> pkg_agent + pkg_tool_todo --> pkg_session + pkg_tool_todo --> pkg_tools + pkg_acp --> pkg_agent + pkg_acp --> pkg_llm + pkg_acp --> pkg_session + pkg_acp --> pkg_session_persistence + pkg_acp --> pkg_tools + pkg_agent_core --> pkg_agent + pkg_agent_core --> pkg_agent_loop + pkg_agent_core --> pkg_invariants + pkg_agent_core --> pkg_llm + pkg_agent_core --> pkg_session + pkg_agent_core --> pkg_system_prompt + pkg_agent_core --> pkg_tool_bash + pkg_agent_core --> pkg_tools + pkg_subagent_acp --> pkg_agent + pkg_subagent_acp --> pkg_llm + pkg_subagent_acp --> pkg_subagent + pkg_subagent_inprocess --> pkg_agent + pkg_subagent_inprocess --> pkg_llm + pkg_subagent_inprocess --> pkg_session + pkg_subagent_inprocess --> pkg_subagent + pkg_tool_subagent --> pkg_agent + pkg_tool_subagent --> pkg_llm + pkg_tool_subagent --> pkg_subagent + pkg_tool_subagent --> pkg_tools + pkg_subagent_mock --> pkg_agent + pkg_subagent_mock --> pkg_llm + pkg_subagent_mock --> pkg_subagent + pkg_subagent_fork --> pkg_agent + pkg_subagent_fork --> pkg_session + pkg_subagent_fork --> pkg_subagent + pkg_subagent_fork --> pkg_subagent_inprocess + pkg_subagent_spawn --> pkg_subagent + pkg_subagent_spawn --> pkg_subagent_inprocess + pkg_acp_agent --> pkg_acp + pkg_acp_agent --> pkg_agent_core + pkg_acp_agent --> pkg_session_persistence_jsonl + pkg_stdio_agent --> pkg_agent + pkg_stdio_agent --> pkg_agent_core + pkg_stdio_agent --> pkg_session + pkg_stdio_agent --> pkg_session_persistence_jsonl + pkg_stdio_agent --> pkg_ui_stdio +``` + +| Package | Group | Depends on | +| --- | --- | --- | +| [`brand`](../../packages/util/brand) | `util` | - | +| [`llm`](../../packages/llm/llm) | `llm` | [`brand`](../../packages/util/brand) | +| [`bash`](../../packages/bash/bash) | `bash` | [`brand`](../../packages/util/brand) | +| [`llm-deepseek`](../../packages/llm/llm-deepseek) | `llm` | [`llm`](../../packages/llm/llm) | +| [`llm-pi-ai`](../../packages/llm/llm-pi-ai) | `llm` | [`llm`](../../packages/llm/llm) | +| [`session`](../../packages/core/session) | `core` | [`brand`](../../packages/util/brand), [`llm`](../../packages/llm/llm) | +| [`system-prompt`](../../packages/core/system-prompt) | `core` | [`llm`](../../packages/llm/llm) | +| [`bash-local`](../../packages/bash/bash-local) | `bash` | [`bash`](../../packages/bash/bash) | +| [`agent`](../../packages/core/agent) | `core` | [`brand`](../../packages/util/brand), [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session) | +| [`compact`](../../packages/compact/compact) | `compact` | [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session) | +| [`session-persistence`](../../packages/session-persistence/session-persistence) | `session-persistence` | [`session`](../../packages/core/session) | +| [`llm-replay`](../../packages/support/llm-replay) | `support` | [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session) | +| [`tools`](../../packages/core/tools) | `core` | [`agent`](../../packages/core/agent), [`llm`](../../packages/llm/llm), [`system-prompt`](../../packages/core/system-prompt) | +| [`compact-basic`](../../packages/compact/compact-basic) | `compact` | [`agent`](../../packages/core/agent), [`compact`](../../packages/compact/compact), [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session) | +| [`session-persistence-jsonl`](../../packages/session-persistence/session-persistence-jsonl) | `session-persistence` | [`session`](../../packages/core/session), [`session-persistence`](../../packages/session-persistence/session-persistence) | +| [`session-persistence-sqlite`](../../packages/session-persistence/session-persistence-sqlite) | `session-persistence` | [`session`](../../packages/core/session), [`session-persistence`](../../packages/session-persistence/session-persistence) | +| [`invariants`](../../packages/support/invariants) | `support` | [`agent`](../../packages/core/agent), [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session) | +| [`ui-stdio`](../../packages/support/ui-stdio) | `support` | [`agent`](../../packages/core/agent), [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session) | +| [`agent-loop`](../../packages/core/agent-loop) | `core` | [`agent`](../../packages/core/agent), [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session), [`session-persistence`](../../packages/session-persistence/session-persistence), [`system-prompt`](../../packages/core/system-prompt), [`tools`](../../packages/core/tools) | +| [`tool-bash`](../../packages/bash/tool-bash) | `bash` | [`agent`](../../packages/core/agent), [`bash`](../../packages/bash/bash), [`llm`](../../packages/llm/llm), [`tools`](../../packages/core/tools) | +| [`subagent`](../../packages/subagent/subagent) | `subagent` | [`agent`](../../packages/core/agent), [`llm`](../../packages/llm/llm), [`tools`](../../packages/core/tools) | +| [`tool-todo`](../../packages/todo/tool-todo) | `todo` | [`agent`](../../packages/core/agent), [`session`](../../packages/core/session), [`tools`](../../packages/core/tools) | +| [`acp`](../../packages/ui/acp) | `ui` | [`agent`](../../packages/core/agent), [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session), [`session-persistence`](../../packages/session-persistence/session-persistence), [`tools`](../../packages/core/tools) | +| [`agent-core`](../../packages/core/agent-core) | `core` | [`agent`](../../packages/core/agent), [`agent-loop`](../../packages/core/agent-loop), [`invariants`](../../packages/support/invariants), [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session), [`system-prompt`](../../packages/core/system-prompt), [`tool-bash`](../../packages/bash/tool-bash), [`tools`](../../packages/core/tools) | +| [`subagent-acp`](../../packages/subagent/subagent-acp) | `subagent` | [`agent`](../../packages/core/agent), [`llm`](../../packages/llm/llm), [`subagent`](../../packages/subagent/subagent) | +| [`subagent-inprocess`](../../packages/subagent/subagent-inprocess) | `subagent` | [`agent`](../../packages/core/agent), [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session), [`subagent`](../../packages/subagent/subagent) | +| [`tool-subagent`](../../packages/subagent/tool-subagent) | `subagent` | [`agent`](../../packages/core/agent), [`llm`](../../packages/llm/llm), [`subagent`](../../packages/subagent/subagent), [`tools`](../../packages/core/tools) | +| [`subagent-mock`](../../packages/support/subagent-mock) | `support` | [`agent`](../../packages/core/agent), [`llm`](../../packages/llm/llm), [`subagent`](../../packages/subagent/subagent) | +| [`subagent-fork`](../../packages/subagent/subagent-fork) | `subagent` | [`agent`](../../packages/core/agent), [`session`](../../packages/core/session), [`subagent`](../../packages/subagent/subagent), [`subagent-inprocess`](../../packages/subagent/subagent-inprocess) | +| [`subagent-spawn`](../../packages/subagent/subagent-spawn) | `subagent` | [`subagent`](../../packages/subagent/subagent), [`subagent-inprocess`](../../packages/subagent/subagent-inprocess) | +| [`acp-agent`](../../packages/ui/acp-agent) | `ui` | [`acp`](../../packages/ui/acp), [`agent-core`](../../packages/core/agent-core), [`session-persistence-jsonl`](../../packages/session-persistence/session-persistence-jsonl) | +| [`stdio-agent`](../../packages/ui/stdio-agent) | `ui` | [`agent`](../../packages/core/agent), [`agent-core`](../../packages/core/agent-core), [`session`](../../packages/core/session), [`session-persistence-jsonl`](../../packages/session-persistence/session-persistence-jsonl), [`ui-stdio`](../../packages/support/ui-stdio) | diff --git a/docs/graphs/session-surface.md b/docs/graphs/session-surface.md new file mode 100644 index 0000000000..b25f85b2d2 --- /dev/null +++ b/docs/graphs/session-surface.md @@ -0,0 +1,25 @@ + + +# Session Surface And Message Projection + +Maintenance mode: curated Mermaid dataflow; exact event/type shapes live in core-data-structures. + +This graph separates the append-only log from the derived message surface the next model request sees. + +```mermaid +flowchart LR + append["Session.append(type, data)"] + log["Append-only SessionEvent log"] + surface["SurfaceManager linked list
surfaceOp + sourceEventSeqs"] + derive["deriveMessages()"] + model["GenerateOptions.messages"] + persist["JSONL / SQLite persistence"] + replay["load / replay / fork seed"] + append --> log + log --> surface + surface --> derive --> model + log --> persist --> replay --> log +``` + +See [core-data-structures/session.md](../core-data-structures/session.md) for the full `SessionEventMap`, surface operations, and turn-enclosure invariant. diff --git a/docs/graphs/snapshot-replay.md b/docs/graphs/snapshot-replay.md new file mode 100644 index 0000000000..d0ee1c9061 --- /dev/null +++ b/docs/graphs/snapshot-replay.md @@ -0,0 +1,24 @@ + + +# ACP Snapshot Replay + +Maintenance mode: curated Mermaid sequence based on the snapshot test harness. + +This graph explains what a snapshot scenario proves: recorded real-model session logs are replayed keylessly, then ACP stdout is normalized and diffed. + +```mermaid +sequenceDiagram + participant Recorder as Real API recording + participant Fixture as snapshot fixture + participant Replay as llm-replay adapter + participant ACP as acp-agent subprocess + participant Golden as stdout golden + Recorder->>Fixture: session.jsonl + workspace inputs + Fixture->>Replay: recorded StreamChunk script + Replay->>ACP: deterministic llm/stream chunks + ACP->>Golden: normalized sessionUpdate stream + Golden-->>ACP: diff must be empty +``` + +Future pressure from the fs stack: policy rejection scenarios are valuable because they prove both world state and failed tool-card rendering, not just that replay returns text. diff --git a/docs/graphs/subagent-lineage.md b/docs/graphs/subagent-lineage.md new file mode 100644 index 0000000000..f687405782 --- /dev/null +++ b/docs/graphs/subagent-lineage.md @@ -0,0 +1,27 @@ + + +# Subagent And Session Lineage + +Maintenance mode: curated Mermaid flow; provider inventory is visible in the generated capability seam graph. + +This graph keeps delegation semantics separate from hook observation. A subagent backend creates an ordinary child agent/session through the shared provider registry. + +```mermaid +flowchart TD + parent["Parent Agent + Session"] + tool["tool-subagent
model-facing name"] + registry["ctx.subagents provider registry"] + spawn["spawn provider
fresh child session"] + fork["fork provider
seeded from completed-turn prefix"] + acp["ACP provider
out-of-process child"] + child["Child AgentHandle
ordinary Agent lifecycle"] + result["SubagentResult returned to tool"] + parent --> tool --> registry + registry --> spawn --> child + registry --> fork --> child + registry --> acp --> child + child --> result --> parent +``` + +The hooks stack adds richer lifecycle observation around child runs; the core ownership rule stays the same: the provider owns the child handle and must dispose it. diff --git a/docs/graphs/tool-affordance-map.md b/docs/graphs/tool-affordance-map.md new file mode 100644 index 0000000000..995dee7b08 --- /dev/null +++ b/docs/graphs/tool-affordance-map.md @@ -0,0 +1,35 @@ + + +# Tool Affordance Map + +Maintenance mode: hybrid: tool names/schemas are boot-harvested from shipped tool plugins; required services and shipped aliases are classified in `scripts/gen-doc-graphs.ts` with a completeness guard. + +This page connects the model-visible tools to the plugin packages and service seams behind them. For exact JSON Schemas, see [tool-catalog/tools.md](../tool-catalog/tools.md). + +```mermaid +flowchart LR + model["Model request tools[]"] + toolpkg__deepseek_ai_dsh_tool_bash["tool-bash
bash, bash_kill, bash_output"] + model --> toolpkg__deepseek_ai_dsh_tool_bash + requires_ctx_tools["ctx.tools"] + toolpkg__deepseek_ai_dsh_tool_bash --> requires_ctx_tools + requires_ctx_bash["ctx.bash"] + toolpkg__deepseek_ai_dsh_tool_bash --> requires_ctx_bash + toolpkg__deepseek_ai_dsh_tool_subagent["tool-subagent
subagent"] + model --> toolpkg__deepseek_ai_dsh_tool_subagent + toolpkg__deepseek_ai_dsh_tool_subagent --> requires_ctx_tools + requires_ctx_subagents["ctx.subagents"] + toolpkg__deepseek_ai_dsh_tool_subagent --> requires_ctx_subagents + toolpkg__deepseek_ai_dsh_tool_todo["tool-todo
todo_write"] + model --> toolpkg__deepseek_ai_dsh_tool_todo + toolpkg__deepseek_ai_dsh_tool_todo --> requires_ctx_tools + requires_owning_Agent_session["owning Agent session"] + toolpkg__deepseek_ai_dsh_tool_todo --> requires_owning_Agent_session +``` + +| Tool package | Model-visible names | Requires | Writes / affects | Shipped aliases | Note | +| --- | --- | --- | --- | --- | --- | +| `@deepseek-ai/dsh-tool-bash` | `bash`, `bash_kill`, `bash_output` | `ctx.tools`, `ctx.bash` | `tool/call`, `tool/result`, `context/message via agent.inject() for background completion notices` | - | The bash/bash_output/bash_kill tools are model-facing consumers of the bash executor seam. | +| `@deepseek-ai/dsh-tool-subagent` | `subagent` | `ctx.tools`, `ctx.subagents` | `tool/call`, `tool/result`, `child session events through the chosen provider` | `subagent`, `subagent_fork` | The default package schema registers subagent; shipped coding/acp configs load it twice to expose spawn and fork backends. | +| `@deepseek-ai/dsh-tool-todo` | `todo_write` | `ctx.tools`, `owning Agent session` | `tool/call`, `todo/write`, `tool/result` | - | todo_write is session-owned state; UIs render the latest todo/write event as a checklist or ACP plan. | diff --git a/docs/graphs/tool-execution-pipeline.md b/docs/graphs/tool-execution-pipeline.md new file mode 100644 index 0000000000..7fde39af83 --- /dev/null +++ b/docs/graphs/tool-execution-pipeline.md @@ -0,0 +1,30 @@ + + +# Tool Execution Pipeline + +Maintenance mode: curated Mermaid flow; exact tool schemas and event signatures live in generated catalogs. + +This graph shows where policy, hooks, sandboxing, and future filesystem guards fit without changing the loop. The key extension point is the `tools/execute` waterfall. + +```mermaid +flowchart TD + model["Assistant message contains tool-call block"] + call["Session event: tool/call"] + waterfall["ctx.tools.execute()
tools/execute waterfall"] + policy["Policy / permission / hooks listener"] + body["Registered tool execute() body"] + owned["Tool-owned session events
todo/write, future fs policy facts"] + result["Session event: tool/result"] + ui["UI presentation
presentCall / presentResult"] + model --> call --> waterfall + waterfall --> policy + policy -->|next()| body + policy -->|veto / throw| result + body --> owned + body --> result + call --> ui + result --> ui +``` + +Future pressure from the fs stack: PR #128 snapshots a policy rejection card. The graph keeps the veto path explicit because filesystem read-before-edit checks, permission prompts, and hook bridges all belong on this path. diff --git a/docs/rfc/README.md b/docs/rfc/README.md index 981710c4f5..8c5b5ebc9b 100644 --- a/docs/rfc/README.md +++ b/docs/rfc/README.md @@ -136,6 +136,7 @@ Do NOT write one for a mechanical or local choice (a variable name, a one-file r | [Generated cordis events + services catalog](implemented/process/2026-06-20-generated-cordis-catalog.md) | 2026-06-20 | | [Classify RFCs by kind via path-encoded subdirectories](implemented/process/2026-06-20-rfc-classification.md) | 2026-06-20 | | [Generated tool-schema catalog (boot-and-harvest)](implemented/process/2026-07-02-tool-schema-catalog.md) | 2026-07-02 | +| [Documentation graph atlas for maintainers and SDK users](implemented/process/2026-07-03-documentation-graph-atlas.md) | 2026-07-03 | ### Testing diff --git a/docs/rfc/implemented/process/2026-07-03-documentation-graph-atlas.md b/docs/rfc/implemented/process/2026-07-03-documentation-graph-atlas.md new file mode 100644 index 0000000000..e707786c14 --- /dev/null +++ b/docs/rfc/implemented/process/2026-07-03-documentation-graph-atlas.md @@ -0,0 +1,66 @@ +# RFC: Documentation graph atlas for maintainers and SDK users + +Status: implemented (accepted 2026-07-03) + +## Context + +The repo already had several high-trust documentation surfaces, each on a different axis: [module-graph.md](../../../module-graph.md) is generated from package `peerDependencies`, [cordis-catalog/events-and-services.md](../../../cordis-catalog/events-and-services.md) is generated from Cordis `Events` and `Context` declarations, [tool-catalog/tools.md](../../../tool-catalog/tools.md) is generated by booting shipped tool plugins, and [core-data-structures/](../../../core-data-structures/core.md) uses `ts type-equiv` blocks to keep pasted type definitions synchronized with source. + +Those references are accurate, but they are mostly catalogs. A maintainer still has to synthesize the relationships: which packages form a capability seam, which app bundles a concrete spine, which event is durable vs live, where a hook or policy plugin can intercept work, and which model-facing tool depends on which service. An SDK user has the same problem from another angle: "Which package do I install or load for the behavior I want, and which event/service/tool do I extend?" + +The pressure is already visible in the open stacks even though this implementation is based on `origin/master`: the hooks stack through PR #129 makes event producer/consumer topology and interception points much more important, while the filesystem stack through PR #128 makes capability seams, policy vetoes, tool presentation, and SDK assembly paths much more important. Graphs based only on today's small bash/todo/subagent surface would become obsolete as soon as those stacks land. + +## Decision + +Add a generated graph atlas under [docs/graphs/](../../../graphs/README.md), produced by `scripts/gen-doc-graphs.ts` and verified by `pnpm run verify-doc-graphs` as part of `doc-sync`. + +The atlas is a relationship layer above the existing catalogs. It does not replace exact references; instead, it links to them and explains how their pieces fit together. + +### Maintenance modes + +Every graph page declares one maintenance mode: + +- **Generated**: all nodes and edges are discovered from source; `--check` fails if the committed artifact is stale. +- **Hybrid generated**: source discovers the inventory, a small manifest classifies irreducible policy, and a completeness guard fails if discovered items are unclassified. +- **Curated**: the diagram explains design intent, temporal order, or ownership; it is emitted by the generator so the atlas remains a single regenerated unit, but the content is deliberately authored. + +### First shipped atlas + +The first atlas ships twelve files: the index plus eleven graph pages. + +| Graph | Maintenance mode | Source of truth | +|---|---|---| +| [package topology by group](../../../graphs/package-topology.md) | generated | `packages/*/*/package.json` peer dependencies plus package group paths | +| [capability seams and core services](../../../graphs/capability-seams.md) | hybrid generated | Cordis service declarations plus a role manifest in `gen-doc-graphs.ts` | +| [app composition](../../../graphs/app-composition.md) | hybrid generated | `examples/*/cordis.yml` plugin lists plus curated app/bundle expansions | +| [event producer/consumer matrix](../../../graphs/event-producer-consumer.md) | hybrid generated | Cordis event declarations, AST-scanned `ctx.on/emit/parallel/serial/waterfall` sites, and explicit dynamic dispatch overrides | +| [tool affordance map](../../../graphs/tool-affordance-map.md) | hybrid generated | boot-harvested tool catalog plus a manifest of required services and shipped aliases | +| [agent turn and step lifecycle](../../../graphs/agent-lifecycle.md) | curated | architecture.md loop lifecycle, Cordis catalog links, and session event semantics | +| [tool execution pipeline](../../../graphs/tool-execution-pipeline.md) | curated | tool pipeline semantics and the `tools/execute` waterfall | +| [session surface and message projection](../../../graphs/session-surface.md) | curated | session surface/event-sourcing docs | +| [subagent and session lineage](../../../graphs/subagent-lineage.md) | curated | subagent seam docs and replay/fork semantics | +| [plugin disposal and hot reload ownership](../../../graphs/hot-reload-disposal.md) | curated | Cordis fiber/effect ownership conventions | +| [ACP snapshot replay](../../../graphs/snapshot-replay.md) | curated | snapshot harness behavior | + +### Why one generator + +Keeping the atlas behind one generator gives reviewers one freshness gate and keeps cross-page terminology synchronized. The tradeoff is that curated diagrams are edited in TypeScript string blocks rather than directly in Markdown. That is acceptable for this first cut because the user-facing artifact is still plain Markdown/Mermaid, and a future change can split the curated pages out if authorship ergonomics matter more than one-command regeneration. + +### Completeness guards + +The hybrid pages must fail loud when their manifests are stale: + +- The capability seam graph imports the Cordis service collector and asserts every discovered harness `ctx.` is classified in `SERVICE_ROLES`, and every classified key still exists. +- The tool affordance graph boot-harvests the shipped tool catalog and asserts every tool package has `TOOL_PACKAGE_META`. +- The event producer/consumer matrix labels itself hybrid because subagent lifecycle events deliberately use `ctx.events.dispatch` for per-listener containment; those dynamic edges are explicit overrides rather than invisible omissions. + +## Format choices + +Use Mermaid for committed diagrams because GitHub renders it in Markdown and it adds no new docs build dependency. Use Markdown tables for dense many-to-many data such as event producer/consumer relationships. Do not adopt PlantUML, hosted diagram services, or generated SVGs until Mermaid becomes the limiting factor. + +## Consequences + +- Maintainers get visual entry points for topology, seams, event flow, lifecycle, session replay, and snapshot behavior. +- SDK users get a path from use case to package composition instead of only bottom-up package references. +- `doc-sync` now includes `verify-doc-graphs`, so graph drift is caught with the other doc freshness gates. +- Future fs and hooks work has a concrete place to land new complexity: fs should expand the capability and tool graphs, while hooks should expand the event matrix and tool execution pipeline. diff --git a/package.json b/package.json index 7d82af4aad..3a35adf5e8 100644 --- a/package.json +++ b/package.json @@ -36,10 +36,12 @@ "verify-cordis-catalog": "tsx scripts/gen-cordis-catalog.ts --check", "gen-tool-catalog": "tsx scripts/gen-tool-catalog.ts", "verify-tool-catalog": "tsx scripts/gen-tool-catalog.ts --check", + "gen-doc-graphs": "tsx scripts/gen-doc-graphs.ts", + "verify-doc-graphs": "tsx scripts/gen-doc-graphs.ts --check", "gen-module-graph": "tsx scripts/gen-module-graph.ts", "verify-module-graph": "tsx scripts/gen-module-graph.ts --check", "constraints": "tsx scripts/check-workspace-constraints.ts", - "doc-sync": "pnpm run doc-typecheck && pnpm run verify-cordis-catalog && pnpm run verify-tool-catalog && pnpm run verify-md-wrap && pnpm run verify-md-links && pnpm run verify-doc-refs && pnpm run verify-package-paths && pnpm run verify-rfc-classification && pnpm run verify-type-equiv", + "doc-sync": "pnpm run doc-typecheck && pnpm run verify-cordis-catalog && pnpm run verify-tool-catalog && pnpm run verify-doc-graphs && pnpm run verify-md-wrap && pnpm run verify-md-links && pnpm run verify-doc-refs && pnpm run verify-package-paths && pnpm run verify-rfc-classification && pnpm run verify-type-equiv", "hygiene": "pnpm run knip && pnpm run publint && pnpm run constraints && pnpm run verify-node-next-types", "demo:echo": "node --expose-internals --import tsx packages/ui/stdio-agent/src/bin.ts examples/echo-agent/cordis.yml", "demo:coding": "node --expose-internals --import tsx packages/ui/stdio-agent/src/bin.ts examples/coding-agent/cordis.yml", diff --git a/scripts/gen-doc-graphs.ts b/scripts/gen-doc-graphs.ts new file mode 100644 index 0000000000..38a385fa62 --- /dev/null +++ b/scripts/gen-doc-graphs.ts @@ -0,0 +1,851 @@ +/** + * Generate (and verify) the documentation graph atlas in docs/graphs/. + * + * This is the relationship layer above the existing catalogs: + * - module-graph.md answers "which packages depend on which packages?" + * - cordis-catalog/ answers "which events and services exist?" + * - tool-catalog/ answers "which tools does the model see?" + * - docs/graphs/ answers "how do those pieces fit together?" + * + * Generated pages discover the enumerable facts from source. Hybrid pages use + * discovered inventory plus small manifests for policy that source cannot infer + * (for example, whether a package is an implementation or consumer in a seam). + * Curated pages are still emitted here so the atlas is one regenerated unit, + * but their diagrams intentionally explain flow and ownership rather than + * pretending to enumerate every source edge. + * + * `tsx scripts/gen-doc-graphs.ts` -> write docs/graphs/*.md + * `tsx scripts/gen-doc-graphs.ts --check` -> exit 1 if any file is stale + */ + +import { existsSync, globSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import ts from 'typescript' +import { collectEvents, collectServices } from './gen-cordis-catalog.ts' +import { collectToolCatalog } from './gen-tool-catalog.ts' + +const root = resolve(import.meta.dirname, '..') +const OUT_DIR = 'docs/graphs' +const SCOPE = '@deepseek-ai/dsh-' + +interface PkgJson { + name: string + peerDependencies?: Record +} + +interface Pkg { + short: string + name: string + group: string + rel: string + deps: string[] +} + +interface GraphDoc { + rel: string + content: string +} + +interface ServiceRole { + key: string + pkg: string + title: string + mode: 'core' | 'seam' | 'bundle' + implementations?: string[] + consumers?: string[] + note: string +} + +interface ExamplePlugin { + id: string + name: string +} + +interface EventRelation { + dispatchers: Map> + listeners: Set +} + +interface ToolPackageMeta { + requires: string[] + writes: string[] + shippedNames?: string[] + note: string +} + +const GROUP_ORDER = ['util', 'llm', 'core', 'bash', 'compact', 'subagent', 'session-persistence', 'todo', 'support', 'ui'] + +const SERVICE_ROLES: ServiceRole[] = [ + { + key: 'llm', + pkg: 'llm', + title: 'LLM adapter registry', + mode: 'seam', + implementations: ['llm-deepseek', 'llm-pi-ai', 'llm-replay'], + consumers: ['agent-loop', 'compact-basic'], + note: 'Adapters register provider implementations; the loop and compaction call the provider-neutral stream service.', + }, + { + key: 'sessions', + pkg: 'session', + title: 'In-memory session store', + mode: 'core', + consumers: ['agent-loop', 'agent', 'session-persistence', 'subagent-inprocess', 'invariants'], + note: 'Owns append-only Session instances and emits the durable session event feed.', + }, + { + key: 'sessionPersistence', + pkg: 'session-persistence', + title: 'Durable session persistence seam', + mode: 'seam', + implementations: ['session-persistence-jsonl', 'session-persistence-sqlite'], + consumers: ['agent-loop', 'acp'], + note: 'Backends persist the same SessionEvent vocabulary; apps choose a backend at composition time.', + }, + { + key: 'systemPrompt', + pkg: 'system-prompt', + title: 'System prompt assembly registry', + mode: 'core', + consumers: ['agent-loop', 'tools'], + note: 'Collects prompt sections and model-facing tool schemas for each step.', + }, + { + key: 'tools', + pkg: 'tools', + title: 'Tool registry and execution waterfall', + mode: 'core', + consumers: ['agent-loop', 'tool-bash', 'tool-subagent', 'tool-todo', 'acp'], + note: 'Registers tool definitions, exposes schemas to the prompt, and routes calls through tools/execute.', + }, + { + key: 'agents', + pkg: 'agent', + title: 'Agent registry', + mode: 'core', + consumers: ['agent-loop', 'acp', 'subagent-inprocess', 'stdio-agent', 'invariants'], + note: 'Owns live Agent handles and the create/resume factory seam.', + }, + { + key: 'agentLoop', + pkg: 'agent-loop', + title: 'Concrete loop driver', + mode: 'bundle', + consumers: ['agent-core'], + note: 'The one concrete loop plugin; extension packages depend on dsh-agent events and services, not on this package.', + }, + { + key: 'bash', + pkg: 'bash', + title: 'Bash executor seam', + mode: 'seam', + implementations: ['bash-local'], + consumers: ['tool-bash'], + note: 'The model-facing bash tools consume this seam; sandboxed or remote executors can replace bash-local.', + }, + { + key: 'compact', + pkg: 'compact', + title: 'Compaction seam', + mode: 'seam', + implementations: ['compact-basic'], + consumers: ['compact-basic'], + note: 'The basic backend currently consumes the pre-step event directly; a model-facing compact tool remains deferred.', + }, + { + key: 'subagents', + pkg: 'subagent', + title: 'Subagent provider registry', + mode: 'seam', + implementations: ['subagent-spawn', 'subagent-fork', 'subagent-acp', 'subagent-mock'], + consumers: ['tool-subagent'], + note: 'Providers implement transports; tool-subagent exposes one configured provider as a model-facing tool name.', + }, +] + +const TOOL_PACKAGE_META: Record = { + '@deepseek-ai/dsh-tool-bash': { + requires: ['ctx.tools', 'ctx.bash'], + writes: ['tool/call', 'tool/result', 'context/message via agent.inject() for background completion notices'], + note: 'The bash/bash_output/bash_kill tools are model-facing consumers of the bash executor seam.', + }, + '@deepseek-ai/dsh-tool-subagent': { + requires: ['ctx.tools', 'ctx.subagents'], + writes: ['tool/call', 'tool/result', 'child session events through the chosen provider'], + shippedNames: ['subagent', 'subagent_fork'], + note: 'The default package schema registers subagent; shipped coding/acp configs load it twice to expose spawn and fork backends.', + }, + '@deepseek-ai/dsh-tool-todo': { + requires: ['ctx.tools', 'owning Agent session'], + writes: ['tool/call', 'todo/write', 'tool/result'], + note: 'todo_write is session-owned state; UIs render the latest todo/write event as a checklist or ACP plan.', + }, +} + +const DYNAMIC_EVENT_DISPATCHERS: Array<{ event: string; pkg: string; method: string }> = [ + // Subagent lifecycle events intentionally bypass ctx.emit and call + // ctx.events.dispatch directly so one throwing listener cannot starve later + // listeners or strand an already-started child run. + { event: 'subagent/start', pkg: 'subagent', method: 'events.dispatch' }, + { event: 'subagent/end', pkg: 'subagent', method: 'events.dispatch' }, +] + +function generatedHeader(title: string, source: string): string[] { + return [ + '', + '', + `# ${title}`, + '', + `Maintenance mode: ${source}.`, + '', + ] +} + +function collectPackages(): Pkg[] { + const pkgs: Pkg[] = [] + for (const rel of globSync('packages/*/*/package.json', { cwd: root }).sort()) { + const json = JSON.parse(readFileSync(resolve(root, rel), 'utf8')) as PkgJson + if (!json.name.startsWith(SCOPE)) continue + const [, group, leaf] = rel.split('/') + if (group === undefined || leaf === undefined) throw new Error(`gen-doc-graphs: unexpected package path ${rel}`) + const deps = Object.keys(json.peerDependencies ?? {}) + .filter(dep => dep.startsWith(SCOPE)) + .map(dep => dep.slice(SCOPE.length)) + .sort() + pkgs.push({ + short: json.name.slice(SCOPE.length), + name: json.name, + group, + rel: dirname(rel), + deps, + }) + } + return topoSort(pkgs) +} + +function topoSort(pkgs: Pkg[]): Pkg[] { + const remaining = new Map(pkgs.map(p => [p.short, p])) + const placed = new Set() + const out: Pkg[] = [] + while (remaining.size > 0) { + const ready = [...remaining.values()] + .filter(pkg => pkg.deps.every(dep => placed.has(dep))) + .sort(comparePackages) + if (ready.length === 0) throw new Error(`gen-doc-graphs: dependency cycle among ${[...remaining.keys()].join(', ')}`) + for (const pkg of ready) { + out.push(pkg) + placed.add(pkg.short) + remaining.delete(pkg.short) + } + } + return out +} + +function comparePackages(a: Pkg, b: Pkg): number { + const groupA = GROUP_ORDER.indexOf(a.group) + const groupB = GROUP_ORDER.indexOf(b.group) + const normA = groupA === -1 ? Number.MAX_SAFE_INTEGER : groupA + const normB = groupB === -1 ? Number.MAX_SAFE_INTEGER : groupB + return normA - normB || a.group.localeCompare(b.group) || a.short.localeCompare(b.short) +} + +function nodeId(prefix: string, value: string): string { + return `${prefix}_${value.replace(/[^a-zA-Z0-9_]/g, '_')}` +} + +function escLabel(value: string): string { + return value.replace(/"/g, '\\"') +} + +function pkgLink(pkg: Pkg | undefined, fallback: string): string { + return pkg ? `[\`${pkg.short}\`](../../${pkg.rel})` : `\`${fallback}\`` +} + +function pkgList(names: string[] | undefined, pkgsByShort: Map): string { + if (!names || names.length === 0) return '-' + return names.map(name => pkgLink(pkgsByShort.get(name), name)).join(', ') +} + +function codeList(values: string[]): string { + return values.length ? values.map(v => `\`${v}\``).join(', ') : '-' +} + +function tableCell(value: string): string { + return value.replace(/\|/g, '\\|').replace(/\n/g, '
') +} + +function renderMermaidPackageNode(pkg: Pkg): string { + return ` ${nodeId('pkg', pkg.short)}["${escLabel(pkg.short)}"]` +} + +function renderPackageTopology(pkgs: Pkg[]): string { + const lines = generatedHeader('Package Topology By Group', 'generated from `packages/*/*/package.json` peer dependencies plus package group paths') + lines.push( + 'This graph complements [module-graph.md](../module-graph.md): it keeps the same canonical peer-dependency edge source, but clusters packages by the `packages//` hierarchy so layering and capability families are easier to scan.', + '', + '```mermaid', + 'flowchart TD', + ) + const groups = [...new Set(pkgs.map(pkg => pkg.group))].sort((a, b) => { + const ia = GROUP_ORDER.indexOf(a) + const ib = GROUP_ORDER.indexOf(b) + const na = ia === -1 ? Number.MAX_SAFE_INTEGER : ia + const nb = ib === -1 ? Number.MAX_SAFE_INTEGER : ib + return na - nb || a.localeCompare(b) + }) + for (const group of groups) { + lines.push(` subgraph ${nodeId('group', group)}["packages/${escLabel(group)}"]`) + for (const pkg of pkgs.filter(p => p.group === group).sort((a, b) => a.short.localeCompare(b.short))) { + lines.push(renderMermaidPackageNode(pkg)) + } + lines.push(' end') + } + for (const pkg of pkgs) { + for (const dep of pkg.deps) lines.push(` ${nodeId('pkg', pkg.short)} --> ${nodeId('pkg', dep)}`) + } + lines.push('```', '', '| Package | Group | Depends on |', '| --- | --- | --- |') + const byShort = new Map(pkgs.map(pkg => [pkg.short, pkg])) + for (const pkg of pkgs) { + lines.push(`| ${pkgLink(pkg, pkg.short)} | \`${pkg.group}\` | ${pkg.deps.length ? pkg.deps.map(dep => pkgLink(byShort.get(dep), dep)).join(', ') : '-'} |`) + } + lines.push('') + return lines.join('\n') +} + +function assertServiceRolesComplete(): void { + const discovered = new Set(collectServices().map(service => service.key)) + const classified = new Set(SERVICE_ROLES.map(role => role.key)) + const missing = [...discovered].filter(key => !classified.has(key)).sort() + const stale = [...classified].filter(key => !discovered.has(key)).sort() + if (missing.length || stale.length) { + throw new Error([ + missing.length ? `missing service role classification: ${missing.join(', ')}` : '', + stale.length ? `stale service role classification: ${stale.join(', ')}` : '', + ].filter(Boolean).join('; ')) + } +} + +function renderCapabilitySeams(pkgs: Pkg[]): string { + assertServiceRolesComplete() + const pkgsByShort = new Map(pkgs.map(pkg => [pkg.short, pkg])) + const nodes = new Map() + const edges = new Set() + const addNode = (id: string, label: string): void => { + if (!nodes.has(id)) nodes.set(id, ` ${id}["${escLabel(label)}"]`) + } + const addEdge = (from: string, to: string): void => { edges.add(` ${from} --> ${to}`) } + const lines = generatedHeader('Capability Seams And Core Services', 'hybrid: services are discovered from Cordis declarations; interface/implementation/consumer roles are classified in `scripts/gen-doc-graphs.ts` with a completeness guard') + lines.push( + 'A service can be a core spine service, a swappable capability seam, or a bundle/composition point. The graph shows the package that owns the service declaration, known implementation packages, and packages that consume the service directly.', + '', + '```mermaid', + 'flowchart LR', + ) + for (const role of SERVICE_ROLES) { + const svc = nodeId('svc', role.key) + const owner = nodeId('pkg', role.pkg) + addNode(owner, role.pkg) + addNode(svc, `ctx.${role.key}
${role.title}`) + addEdge(owner, svc) + for (const impl of role.implementations ?? []) { + addNode(nodeId('pkg', impl), impl) + addEdge(nodeId('pkg', impl), svc) + } + for (const consumer of role.consumers ?? []) { + addNode(nodeId('pkg', consumer), consumer) + addEdge(svc, nodeId('pkg', consumer)) + } + } + lines.push(...nodes.values(), ...[...edges].sort()) + lines.push('```', '', '| ctx key | Role | Owner | Implementations | Direct consumers | Note |', '| --- | --- | --- | --- | --- | --- |') + for (const role of SERVICE_ROLES) { + lines.push(`| \`ctx.${role.key}\` | \`${role.mode}\` | ${pkgLink(pkgsByShort.get(role.pkg), role.pkg)} | ${pkgList(role.implementations, pkgsByShort)} | ${pkgList(role.consumers, pkgsByShort)} | ${tableCell(role.note)} |`) + } + lines.push('') + return lines.join('\n') +} + +function parseExampleCordis(rel: string): ExamplePlugin[] { + const text = readFileSync(resolve(root, rel), 'utf8') + const plugins: ExamplePlugin[] = [] + let current: { id: string; name?: string } | null = null + const flush = (): void => { + if (current?.name) plugins.push({ id: current.id, name: current.name }) + } + for (const line of text.split('\n')) { + const id = /^-\s+id:\s+(.+?)\s*$/.exec(line) + if (id?.[1] !== undefined) { + flush() + current = { id: stripYamlScalar(id[1]) } + continue + } + const name = /^\s+name:\s+(.+?)\s*$/.exec(line) + if (name?.[1] !== undefined && current) current.name = stripYamlScalar(name[1]) + } + flush() + return plugins +} + +function stripYamlScalar(value: string): string { + return value.trim().replace(/^['"]|['"]$/g, '') +} + +function renderAppComposition(): string { + const examples = [ + { id: 'echo', label: 'examples/echo-agent', config: 'examples/echo-agent/cordis.yml' }, + { id: 'coding', label: 'examples/coding-agent', config: 'examples/coding-agent/cordis.yml' }, + { id: 'acp', label: 'examples/acp-agent', config: 'examples/acp-agent/cordis.yml' }, + ] + const lines = generatedHeader('App Composition', 'hybrid: leaf plugin lists are parsed from `examples/*/cordis.yml`; bundle expansions are curated from app package source') + lines.push( + 'This graph is for SDK users asking which pieces a runnable agent loads. Leaf configs choose adapters and optional product tools; app packages provide the front door; `dsh-agent-core` bundles the providerless spine.', + '', + '```mermaid', + 'flowchart LR', + ) + const bundleTargets: Record = { + '@deepseek-ai/dsh-stdio-agent': nodeId('bundle', 'stdio'), + '@deepseek-ai/dsh-acp-agent': nodeId('bundle', 'acp_agent'), + } + for (const example of examples) { + lines.push(` subgraph ${nodeId('example', example.id)}["${escLabel(example.label)}"]`) + lines.push(` ${nodeId('cfg', example.id)}["cordis.yml"]`) + for (const plugin of parseExampleCordis(example.config)) { + const pluginNode = nodeId(`plugin_${example.id}`, plugin.id) + lines.push(` ${pluginNode}["${escLabel(plugin.id)}
${escLabel(plugin.name)}"]`) + lines.push(` ${nodeId('cfg', example.id)} --> ${pluginNode}`) + const bundle = bundleTargets[plugin.name] + if (bundle !== undefined) lines.push(` ${pluginNode} --> ${bundle}`) + } + lines.push(' end') + } + lines.push( + ` ${nodeId('bundle', 'stdio')}["@deepseek-ai/dsh-stdio-agent"] --> ${nodeId('bundle', 'agent_core')}["@deepseek-ai/dsh-agent-core"]`, + ` ${nodeId('bundle', 'stdio')} --> ${nodeId('bundle', 'jsonl')}["@deepseek-ai/dsh-session-persistence-jsonl"]`, + ` ${nodeId('bundle', 'stdio')} --> ${nodeId('bundle', 'ui_stdio')}["@deepseek-ai/dsh-ui-stdio"]`, + ` ${nodeId('bundle', 'acp_agent')}["@deepseek-ai/dsh-acp-agent"] --> ${nodeId('bundle', 'agent_core')}`, + ` ${nodeId('bundle', 'acp_agent')} --> ${nodeId('bundle', 'jsonl')}`, + ` ${nodeId('bundle', 'acp_agent')} --> ${nodeId('bundle', 'acp')}["@deepseek-ai/dsh-acp"]`, + ` ${nodeId('bundle', 'agent_core')} --> ${nodeId('spine', 'llm')}["ctx.llm"]`, + ` ${nodeId('bundle', 'agent_core')} --> ${nodeId('spine', 'sessions')}["ctx.sessions"]`, + ` ${nodeId('bundle', 'agent_core')} --> ${nodeId('spine', 'tools')}["ctx.tools + tool-bash"]`, + ` ${nodeId('bundle', 'agent_core')} --> ${nodeId('spine', 'loop')}["ctx.agents + ctx.agentLoop"]`, + '```', + '', + '| Example | Parsed plugin ids | Config |', + '| --- | --- | --- |', + ) + for (const example of examples) { + const plugins = parseExampleCordis(example.config) + lines.push(`| \`${example.label}\` | ${plugins.map(plugin => `\`${plugin.id}\``).join(', ')} | [\`${example.config}\`](../../${example.config}) |`) + } + lines.push('') + return lines.join('\n') +} + +function collectEventRelations(): Map { + const out = new Map() + const ensure = (event: string): EventRelation => { + const existing = out.get(event) + if (existing) return existing + const next = { dispatchers: new Map>(), listeners: new Set() } + out.set(event, next) + return next + } + for (const rel of globSync('packages/*/*/src/**/*.ts', { cwd: root }).sort()) { + const [, , leaf] = rel.split('/') + if (leaf === undefined) continue + const text = readFileSync(resolve(root, rel), 'utf8') + const sf = ts.createSourceFile(rel, text, ts.ScriptTarget.Latest, true) + const visit = (node: ts.Node): void => { + if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) { + const method = node.expression.name.text + if (!isCordisContextReceiver(node.expression, sf)) { + ts.forEachChild(node, visit) + return + } + if (method === 'on') { + const event = eventArg(node.arguments, method) + if (event) ensure(event).listeners.add(leaf) + } else if (method === 'emit' || method === 'parallel' || method === 'serial' || method === 'waterfall') { + const event = eventArg(node.arguments, method) + if (event) { + const relation = ensure(event) + const methods = relation.dispatchers.get(leaf) ?? new Set() + methods.add(method) + relation.dispatchers.set(leaf, methods) + } + } + } + ts.forEachChild(node, visit) + } + visit(sf) + } + for (const entry of DYNAMIC_EVENT_DISPATCHERS) { + const relation = ensure(entry.event) + const methods = relation.dispatchers.get(entry.pkg) ?? new Set() + methods.add(entry.method) + relation.dispatchers.set(entry.pkg, methods) + } + return out +} + +function isCordisContextReceiver(expr: ts.PropertyAccessExpression, sf: ts.SourceFile): boolean { + const target = expr.expression.getText(sf) + return target === 'ctx' || target === 'this.ctx' +} + +function eventArg(args: ts.NodeArray, method: string): string | undefined { + if (method === 'waterfall') { + const arg = args.find(ts.isStringLiteralLike) + return arg?.text + } + const first = args[0] + return first && ts.isStringLiteralLike(first) ? first.text : undefined +} + +function relationPackages(map: Map>, pkgsByShort: Map): string { + if (map.size === 0) return '-' + return [...map.entries()] + .sort(([a], [b]) => a.localeCompare(b)) + .map(([pkg, methods]) => `${pkgLink(pkgsByShort.get(pkg), pkg)} (${[...methods].sort().map(m => `\`${m}\``).join(', ')})`) + .join(', ') +} + +function listenerPackages(listeners: Set, pkgsByShort: Map): string { + if (listeners.size === 0) return '-' + return [...listeners].sort().map(pkg => pkgLink(pkgsByShort.get(pkg), pkg)).join(', ') +} + +function renderEventRelations(pkgs: Pkg[]): string { + const events = collectEvents() + const relations = collectEventRelations() + const pkgsByShort = new Map(pkgs.map(pkg => [pkg.short, pkg])) + const lines = generatedHeader('Event Producer And Consumer Matrix', 'hybrid generated: Cordis event declarations and most producer/listener edges are AST-scanned; dynamic dispatch sites are classified in `scripts/gen-doc-graphs.ts`') + lines.push( + 'This matrix shows which packages dispatch each harness-owned event and which packages listen to it. It is intentionally a table rather than one large graph: events are many-to-many, and dense relation data is easier to review in rows. Dynamic dispatch overrides cover sites that deliberately bypass `ctx.emit`, such as subagent lifecycle containment.', + '', + '| Event | Mode | Declared in | Dispatchers | Listeners |', + '| --- | --- | --- | --- | --- |', + ) + for (const event of [...events].sort((a, b) => a.name.localeCompare(b.name))) { + const relation = relations.get(event.name) ?? { dispatchers: new Map>(), listeners: new Set() } + lines.push(`| \`${event.name}\` | \`${event.mode}\` | [\`${event.source}\`](../../${event.source.split(':')[0]}) | ${relationPackages(relation.dispatchers, pkgsByShort)} | ${listenerPackages(relation.listeners, pkgsByShort)} |`) + } + const declared = new Set(events.map(event => event.name)) + const extra = [...relations.keys()].filter(event => !declared.has(event)).sort() + if (extra.length > 0) { + lines.push('', '## Non-harness or undeclared event strings seen in package source', '', '| Event string | Dispatchers | Listeners |', '| --- | --- | --- |') + for (const event of extra) { + const relation = relations.get(event) + if (!relation) continue + lines.push(`| \`${event}\` | ${relationPackages(relation.dispatchers, pkgsByShort)} | ${listenerPackages(relation.listeners, pkgsByShort)} |`) + } + } + lines.push('') + return lines.join('\n') +} + +async function renderToolAffordance(): Promise { + const catalog = await collectToolCatalog() + const lines = generatedHeader('Tool Affordance Map', 'hybrid: tool names/schemas are boot-harvested from shipped tool plugins; required services and shipped aliases are classified in `scripts/gen-doc-graphs.ts` with a completeness guard') + for (const entry of catalog) { + if (!TOOL_PACKAGE_META[entry.pkg]) { + throw new Error(`gen-doc-graphs: tool package ${entry.pkg} is missing TOOL_PACKAGE_META classification`) + } + } + lines.push( + 'This page connects the model-visible tools to the plugin packages and service seams behind them. For exact JSON Schemas, see [tool-catalog/tools.md](../tool-catalog/tools.md).', + '', + '```mermaid', + 'flowchart LR', + ' model["Model request tools[]"]', + ) + const requirementNodes = new Set() + for (const entry of catalog) { + const meta = TOOL_PACKAGE_META[entry.pkg] + if (!meta) continue + const packageNode = nodeId('toolpkg', entry.pkg) + const names = entry.schemas.map(schema => schema.name).join(', ') + lines.push(` ${packageNode}["${escLabel(entry.pkg.replace(SCOPE, ''))}
${escLabel(names)}"]`) + lines.push(` model --> ${packageNode}`) + for (const req of meta.requires) { + const reqNode = nodeId('requires', req) + if (!requirementNodes.has(reqNode)) { + lines.push(` ${reqNode}["${escLabel(req)}"]`) + requirementNodes.add(reqNode) + } + lines.push(` ${packageNode} --> ${reqNode}`) + } + } + lines.push('```', '', '| Tool package | Model-visible names | Requires | Writes / affects | Shipped aliases | Note |', '| --- | --- | --- | --- | --- | --- |') + for (const entry of catalog) { + const meta = TOOL_PACKAGE_META[entry.pkg] + if (!meta) continue + lines.push(`| \`${entry.pkg}\` | ${codeList(entry.schemas.map(schema => schema.name))} | ${codeList(meta.requires)} | ${codeList(meta.writes)} | ${codeList(meta.shippedNames ?? [])} | ${tableCell(meta.note)} |`) + } + lines.push('') + return lines.join('\n') +} + +function renderLifecycle(): string { + return [ + ...generatedHeader('Agent Turn And Step Lifecycle', 'curated Mermaid sequence; exact event signatures live in the generated Cordis catalog'), + 'This sequence is the visual companion to [architecture.md](../architecture.md#loop-lifecycle-session--turn--step). It shows the durable session event path separately from live `agent/*` notifications.', + '', + '```mermaid', + 'sequenceDiagram', + ' participant User', + ' participant Agent', + ' participant Loop', + ' participant Prompt as ctx.systemPrompt', + ' participant LLM as ctx.llm', + ' participant Tools as ctx.tools', + ' participant Session', + ' participant Persistence', + ' User->>Agent: send(content)', + ' Agent->>Loop: queued work wakes driver', + ' Loop->>Session: turn/start + user/message', + ' Loop-->>User: agent/turn-start', + ' Loop->>Prompt: system-prompt/assemble waterfall', + ' Loop-->>Loop: agent/pre-step serial checkpoint', + ' Loop->>Session: step/start', + ' Loop->>LLM: agent/request waterfall, then llm/stream waterfall', + ' LLM-->>Loop: StreamChunk*', + ' Loop->>Session: assistant/chunk*', + ' Loop-->>User: agent/stream-chunk* (master live mirror)', + ' Loop->>Session: assistant/message', + ' Loop->>Tools: tools/execute waterfall for each tool-call', + ' Tools-->>Session: tool-owned events when applicable', + ' Loop->>Session: tool/result', + ' Loop-->>Loop: agent/turn-continuation waterfall', + ' Loop->>Session: turn/end', + ' Loop->>Persistence: session/flush parallel checkpoint', + ' Loop-->>User: agent/status idle', + '```', + '', + 'Future pressure from the hooks stack: PR #129 removes the live `agent/stream-chunk` mirror and leaves durable `assistant/chunk` on `session/event` as the authoritative token stream. Consumers that need replayable transcript data should already treat `session/event` as the load-bearing path.', + '', + ].join('\n') +} + +function renderToolPipeline(): string { + return [ + ...generatedHeader('Tool Execution Pipeline', 'curated Mermaid flow; exact tool schemas and event signatures live in generated catalogs'), + 'This graph shows where policy, hooks, sandboxing, and future filesystem guards fit without changing the loop. The key extension point is the `tools/execute` waterfall.', + '', + '```mermaid', + 'flowchart TD', + ' model["Assistant message contains tool-call block"]', + ' call["Session event: tool/call"]', + ' waterfall["ctx.tools.execute()
tools/execute waterfall"]', + ' policy["Policy / permission / hooks listener"]', + ' body["Registered tool execute() body"]', + ' owned["Tool-owned session events
todo/write, future fs policy facts"]', + ' result["Session event: tool/result"]', + ' ui["UI presentation
presentCall / presentResult"]', + ' model --> call --> waterfall', + ' waterfall --> policy', + ' policy -->|next()| body', + ' policy -->|veto / throw| result', + ' body --> owned', + ' body --> result', + ' call --> ui', + ' result --> ui', + '```', + '', + 'Future pressure from the fs stack: PR #128 snapshots a policy rejection card. The graph keeps the veto path explicit because filesystem read-before-edit checks, permission prompts, and hook bridges all belong on this path.', + '', + ].join('\n') +} + +function renderSessionSurface(): string { + return [ + ...generatedHeader('Session Surface And Message Projection', 'curated Mermaid dataflow; exact event/type shapes live in core-data-structures'), + 'This graph separates the append-only log from the derived message surface the next model request sees.', + '', + '```mermaid', + 'flowchart LR', + ' append["Session.append(type, data)"]', + ' log["Append-only SessionEvent log"]', + ' surface["SurfaceManager linked list
surfaceOp + sourceEventSeqs"]', + ' derive["deriveMessages()"]', + ' model["GenerateOptions.messages"]', + ' persist["JSONL / SQLite persistence"]', + ' replay["load / replay / fork seed"]', + ' append --> log', + ' log --> surface', + ' surface --> derive --> model', + ' log --> persist --> replay --> log', + '```', + '', + 'See [core-data-structures/session.md](../core-data-structures/session.md) for the full `SessionEventMap`, surface operations, and turn-enclosure invariant.', + '', + ].join('\n') +} + +function renderSubagentLineage(): string { + return [ + ...generatedHeader('Subagent And Session Lineage', 'curated Mermaid flow; provider inventory is visible in the generated capability seam graph'), + 'This graph keeps delegation semantics separate from hook observation. A subagent backend creates an ordinary child agent/session through the shared provider registry.', + '', + '```mermaid', + 'flowchart TD', + ' parent["Parent Agent + Session"]', + ' tool["tool-subagent
model-facing name"]', + ' registry["ctx.subagents provider registry"]', + ' spawn["spawn provider
fresh child session"]', + ' fork["fork provider
seeded from completed-turn prefix"]', + ' acp["ACP provider
out-of-process child"]', + ' child["Child AgentHandle
ordinary Agent lifecycle"]', + ' result["SubagentResult returned to tool"]', + ' parent --> tool --> registry', + ' registry --> spawn --> child', + ' registry --> fork --> child', + ' registry --> acp --> child', + ' child --> result --> parent', + '```', + '', + 'The hooks stack adds richer lifecycle observation around child runs; the core ownership rule stays the same: the provider owns the child handle and must dispose it.', + '', + ].join('\n') +} + +function renderHotReload(): string { + return [ + ...generatedHeader('Plugin Disposal And Hot Reload Ownership', 'curated Mermaid flow based on Cordis fiber/effect conventions'), + 'This graph is a maintainer checklist for plugin authors: registrations are effects, service injection gates activation, and owned handles must be disposed by their owner.', + '', + '```mermaid', + 'flowchart TD', + ' plugin["ctx.plugin(plugin) creates fiber"]', + ' inject["static inject gates activation"]', + ' service["ctx.provide / Service constructor"]', + ' effects["ctx.effect registrations
events, tools, adapters, timers"]', + ' reload["HMR / fiber.dispose()"]', + ' disposers["Run disposers in owner fiber"]', + ' quiescence["Owned AgentHandle.dispose()
or service teardown awaits quiescence"]', + ' plugin --> inject --> service', + ' inject --> effects', + ' reload --> disposers --> quiescence', + '```', + '', + 'Hook bridges and SDK plugins increase the number of long-lived listeners, so this ownership graph should stay small and visible.', + '', + ].join('\n') +} + +function renderSnapshotReplay(): string { + return [ + ...generatedHeader('ACP Snapshot Replay', 'curated Mermaid sequence based on the snapshot test harness'), + 'This graph explains what a snapshot scenario proves: recorded real-model session logs are replayed keylessly, then ACP stdout is normalized and diffed.', + '', + '```mermaid', + 'sequenceDiagram', + ' participant Recorder as Real API recording', + ' participant Fixture as snapshot fixture', + ' participant Replay as llm-replay adapter', + ' participant ACP as acp-agent subprocess', + ' participant Golden as stdout golden', + ' Recorder->>Fixture: session.jsonl + workspace inputs', + ' Fixture->>Replay: recorded StreamChunk script', + ' Replay->>ACP: deterministic llm/stream chunks', + ' ACP->>Golden: normalized sessionUpdate stream', + ' Golden-->>ACP: diff must be empty', + '```', + '', + 'Future pressure from the fs stack: policy rejection scenarios are valuable because they prove both world state and failed tool-card rendering, not just that replay returns text.', + '', + ].join('\n') +} + +async function renderDocs(): Promise { + const pkgs = collectPackages() + const docs: GraphDoc[] = [ + { rel: `${OUT_DIR}/package-topology.md`, content: renderPackageTopology(pkgs) }, + { rel: `${OUT_DIR}/capability-seams.md`, content: renderCapabilitySeams(pkgs) }, + { rel: `${OUT_DIR}/app-composition.md`, content: renderAppComposition() }, + { rel: `${OUT_DIR}/event-producer-consumer.md`, content: renderEventRelations(pkgs) }, + { rel: `${OUT_DIR}/tool-affordance-map.md`, content: await renderToolAffordance() }, + { rel: `${OUT_DIR}/agent-lifecycle.md`, content: renderLifecycle() }, + { rel: `${OUT_DIR}/tool-execution-pipeline.md`, content: renderToolPipeline() }, + { rel: `${OUT_DIR}/session-surface.md`, content: renderSessionSurface() }, + { rel: `${OUT_DIR}/subagent-lineage.md`, content: renderSubagentLineage() }, + { rel: `${OUT_DIR}/hot-reload-disposal.md`, content: renderHotReload() }, + { rel: `${OUT_DIR}/snapshot-replay.md`, content: renderSnapshotReplay() }, + ] + docs.unshift({ rel: `${OUT_DIR}/README.md`, content: renderIndex(docs) }) + return docs +} + +function renderIndex(docs: GraphDoc[]): string { + const labels: Record = { + 'package-topology.md': 'package topology by group', + 'capability-seams.md': 'capability seams and core services', + 'app-composition.md': 'app composition', + 'event-producer-consumer.md': 'event producer/consumer matrix', + 'tool-affordance-map.md': 'tool affordance map', + 'agent-lifecycle.md': 'agent turn and step lifecycle', + 'tool-execution-pipeline.md': 'tool execution pipeline', + 'session-surface.md': 'session surface and message projection', + 'subagent-lineage.md': 'subagent and session lineage', + 'hot-reload-disposal.md': 'plugin disposal and hot reload ownership', + 'snapshot-replay.md': 'ACP snapshot replay', + } + const modes: Record = { + 'package-topology.md': 'generated', + 'capability-seams.md': 'hybrid generated', + 'app-composition.md': 'hybrid generated', + 'event-producer-consumer.md': 'hybrid generated', + 'tool-affordance-map.md': 'hybrid generated', + 'agent-lifecycle.md': 'curated', + 'tool-execution-pipeline.md': 'curated', + 'session-surface.md': 'curated', + 'subagent-lineage.md': 'curated', + 'hot-reload-disposal.md': 'curated', + 'snapshot-replay.md': 'curated', + } + return [ + ...generatedHeader('Documentation Graph Atlas', 'mixed: each linked page declares generated, hybrid, or curated mode'), + 'The graph atlas is the relationship layer above the generated catalogs. Use it to navigate package topology, capability seams, event flow, model-facing tools, and runtime lifecycle paths. Exact signatures and type shapes still live in [cordis-catalog/](../cordis-catalog/events-and-services.md), [tool-catalog/](../tool-catalog/tools.md), and [core-data-structures/](../core-data-structures/core.md).', + '', + 'The process decision behind this atlas is recorded in [the documentation graph atlas RFC](../rfc/implemented/process/2026-07-03-documentation-graph-atlas.md).', + '', + '| Graph | Mode |', + '| --- | --- |', + ...docs.map((doc) => { + const file = doc.rel.split('/').at(-1) ?? doc.rel + return `| [${labels[file] ?? file}](${file}) | \`${modes[file] ?? 'generated'}\` |` + }), + '', + 'Regenerate with `pnpm run gen-doc-graphs`; verify freshness with `pnpm run verify-doc-graphs`.', + '', + ].join('\n') +} + +async function main(): Promise { + const docs = await renderDocs() + if (process.argv.includes('--check')) { + const stale: string[] = [] + for (const doc of docs) { + const abs = resolve(root, doc.rel) + const committed = existsSync(abs) ? readFileSync(abs, 'utf8') : null + if (committed !== doc.content) stale.push(doc.rel) + } + if (stale.length === 0) { + console.log(`gen-doc-graphs: ${docs.length} graph doc(s) are up to date.`) + return + } + console.error(`gen-doc-graphs: stale graph doc(s): ${stale.join(', ')}. Run \`pnpm run gen-doc-graphs\` and commit the result.`) + process.exit(1) + } + + mkdirSync(resolve(root, OUT_DIR), { recursive: true }) + for (const doc of docs) writeFileSync(resolve(root, doc.rel), doc.content) + console.log(`gen-doc-graphs: wrote ${docs.length} graph doc(s).`) +} + +if (process.argv[1] && import.meta.filename === resolve(process.argv[1])) { + await main() +} From 8caf9231967a4761d6c1cd31ee5516f319864969 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Fri, 3 Jul 2026 01:32:01 +0800 Subject: [PATCH 02/14] docs(graphs): verify mermaid syntax --- .github/workflows/ci.yml | 6 +- docs/development.md | 1 + docs/graphs/agent-lifecycle.md | 36 +- docs/graphs/tool-execution-pipeline.md | 20 +- .../2026-07-03-documentation-graph-atlas.md | 3 +- package.json | 6 +- pnpm-lock.yaml | 1157 ++++++++++++++++- scripts/gen-doc-graphs.ts | 56 +- scripts/verify-mermaid.ts | 107 ++ 9 files changed, 1328 insertions(+), 64 deletions(-) create mode 100644 scripts/verify-mermaid.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9064a38cea..4212717339 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,10 +49,10 @@ jobs: # Doc-sync gates (doc-sync-enforcement RFC). doc-typecheck compiles the # fenced ts blocks against the root project-reference graph. The cordis - # catalog freshness check, type-equiv check, and markdown wrap/link checks - # only read source. Same `doc-sync` script the pre-push hook runs + # catalog freshness check, type-equiv check, Mermaid syntax check, and + # markdown wrap/link checks only read source. Same `doc-sync` script the pre-push hook runs # (quality-gates RFC: one source of truth). - - name: Doc-sync gates (doc code blocks + cordis catalog + type-equiv + markdown wrap/links) + - name: Doc-sync gates (doc code blocks + catalogs + mermaid + markdown) run: pnpm run doc-sync # Module-graph freshness: regenerate docs/module-graph.md from the diff --git a/docs/development.md b/docs/development.md index 965f23d90f..03a2c73c62 100644 --- a/docs/development.md +++ b/docs/development.md @@ -99,6 +99,7 @@ pnpm run verify-cordis-catalog # fail if the cordis events/services catalog is pnpm run gen-doc-graphs # regenerate docs/graphs/*.md from source and curated graph definitions pnpm run verify-doc-graphs # fail if docs/graphs/*.md is stale pnpm run verify-md-wrap # fail on hard-wrapped prose paragraphs in docs/README markdown +pnpm run verify-mermaid # fail if a ```mermaid diagram has invalid Mermaid syntax pnpm run verify-type-equiv # fail if a ```ts type-equiv doc block drifts from its source type pnpm run doc-sync # doc-typecheck, generated doc freshness, markdown wrap/link, and type-equiv verification pnpm run gen-module-graph # regenerate docs/module-graph.md from package peerDeps diff --git a/docs/graphs/agent-lifecycle.md b/docs/graphs/agent-lifecycle.md index cc25e25694..36c8702a95 100644 --- a/docs/graphs/agent-lifecycle.md +++ b/docs/graphs/agent-lifecycle.md @@ -11,31 +11,31 @@ This sequence is the visual companion to [architecture.md](../architecture.md#lo sequenceDiagram participant User participant Agent - participant Loop + participant Driver participant Prompt as ctx.systemPrompt participant LLM as ctx.llm participant Tools as ctx.tools participant Session participant Persistence User->>Agent: send(content) - Agent->>Loop: queued work wakes driver - Loop->>Session: turn/start + user/message - Loop-->>User: agent/turn-start - Loop->>Prompt: system-prompt/assemble waterfall - Loop-->>Loop: agent/pre-step serial checkpoint - Loop->>Session: step/start - Loop->>LLM: agent/request waterfall, then llm/stream waterfall - LLM-->>Loop: StreamChunk* - Loop->>Session: assistant/chunk* - Loop-->>User: agent/stream-chunk* (master live mirror) - Loop->>Session: assistant/message - Loop->>Tools: tools/execute waterfall for each tool-call + Agent->>Driver: queued work wakes driver + Driver->>Session: turn/start + user/message + Driver-->>User: agent/turn-start + Driver->>Prompt: system-prompt/assemble waterfall + Driver-->>Driver: agent/pre-step serial checkpoint + Driver->>Session: step/start + Driver->>LLM: agent/request waterfall, then llm/stream waterfall + LLM-->>Driver: StreamChunk* + Driver->>Session: assistant/chunk* + Driver-->>User: agent/stream-chunk* (master live mirror) + Driver->>Session: assistant/message + Driver->>Tools: tools/execute waterfall for each tool-call Tools-->>Session: tool-owned events when applicable - Loop->>Session: tool/result - Loop-->>Loop: agent/turn-continuation waterfall - Loop->>Session: turn/end - Loop->>Persistence: session/flush parallel checkpoint - Loop-->>User: agent/status idle + Driver->>Session: tool/result + Driver-->>Driver: agent/turn-continuation waterfall + Driver->>Session: turn/end + Driver->>Persistence: session/flush parallel checkpoint + Driver-->>User: agent/status idle ``` Future pressure from the hooks stack: PR #129 removes the live `agent/stream-chunk` mirror and leaves durable `assistant/chunk` on `session/event` as the authoritative token stream. Consumers that need replayable transcript data should already treat `session/event` as the load-bearing path. diff --git a/docs/graphs/tool-execution-pipeline.md b/docs/graphs/tool-execution-pipeline.md index 7fde39af83..079250ca90 100644 --- a/docs/graphs/tool-execution-pipeline.md +++ b/docs/graphs/tool-execution-pipeline.md @@ -10,21 +10,21 @@ This graph shows where policy, hooks, sandboxing, and future filesystem guards f ```mermaid flowchart TD model["Assistant message contains tool-call block"] - call["Session event: tool/call"] + toolCall["Session event: tool/call"] waterfall["ctx.tools.execute()
tools/execute waterfall"] policy["Policy / permission / hooks listener"] - body["Registered tool execute() body"] + toolBody["Registered tool execute() body"] owned["Tool-owned session events
todo/write, future fs policy facts"] - result["Session event: tool/result"] + toolResult["Session event: tool/result"] ui["UI presentation
presentCall / presentResult"] - model --> call --> waterfall + model --> toolCall --> waterfall waterfall --> policy - policy -->|next()| body - policy -->|veto / throw| result - body --> owned - body --> result - call --> ui - result --> ui + policy -->|next| toolBody + policy -->|veto / throw| toolResult + toolBody --> owned + toolBody --> toolResult + toolCall --> ui + toolResult --> ui ``` Future pressure from the fs stack: PR #128 snapshots a policy rejection card. The graph keeps the veto path explicit because filesystem read-before-edit checks, permission prompts, and hook bridges all belong on this path. diff --git a/docs/rfc/implemented/process/2026-07-03-documentation-graph-atlas.md b/docs/rfc/implemented/process/2026-07-03-documentation-graph-atlas.md index e707786c14..97c7b13bf6 100644 --- a/docs/rfc/implemented/process/2026-07-03-documentation-graph-atlas.md +++ b/docs/rfc/implemented/process/2026-07-03-documentation-graph-atlas.md @@ -53,6 +53,7 @@ The hybrid pages must fail loud when their manifests are stale: - The capability seam graph imports the Cordis service collector and asserts every discovered harness `ctx.` is classified in `SERVICE_ROLES`, and every classified key still exists. - The tool affordance graph boot-harvests the shipped tool catalog and asserts every tool package has `TOOL_PACKAGE_META`. - The event producer/consumer matrix labels itself hybrid because subagent lifecycle events deliberately use `ctx.events.dispatch` for per-listener containment; those dynamic edges are explicit overrides rather than invisible omissions. +- `verify-mermaid` parses every repo-authored ` ```mermaid ` fence with Mermaid's own parser, so syntax errors fail `doc-sync` locally and in CI instead of showing up as broken GitHub-rendered diagrams. ## Format choices @@ -62,5 +63,5 @@ Use Mermaid for committed diagrams because GitHub renders it in Markdown and it - Maintainers get visual entry points for topology, seams, event flow, lifecycle, session replay, and snapshot behavior. - SDK users get a path from use case to package composition instead of only bottom-up package references. -- `doc-sync` now includes `verify-doc-graphs`, so graph drift is caught with the other doc freshness gates. +- `doc-sync` now includes `verify-doc-graphs` and `verify-mermaid`, so graph drift and Mermaid syntax errors are caught with the other doc freshness gates. - Future fs and hooks work has a concrete place to land new complexity: fs should expand the capability and tool graphs, while hooks should expand the event matrix and tool execution pipeline. diff --git a/package.json b/package.json index 3a35adf5e8..48bd1ae96b 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "verify-md-links": "tsx scripts/verify-md-links.ts", "verify-doc-refs": "tsx scripts/verify-doc-refs.ts", "verify-package-paths": "tsx scripts/verify-package-paths.ts", + "verify-mermaid": "tsx scripts/verify-mermaid.ts", "verify-rfc-classification": "tsx scripts/verify-rfc-classification.ts", "verify-type-equiv": "tsx scripts/verify-type-equiv.ts", "verify-node-next-types": "tsx scripts/verify-node-next-types.ts", @@ -41,7 +42,7 @@ "gen-module-graph": "tsx scripts/gen-module-graph.ts", "verify-module-graph": "tsx scripts/gen-module-graph.ts --check", "constraints": "tsx scripts/check-workspace-constraints.ts", - "doc-sync": "pnpm run doc-typecheck && pnpm run verify-cordis-catalog && pnpm run verify-tool-catalog && pnpm run verify-doc-graphs && pnpm run verify-md-wrap && pnpm run verify-md-links && pnpm run verify-doc-refs && pnpm run verify-package-paths && pnpm run verify-rfc-classification && pnpm run verify-type-equiv", + "doc-sync": "pnpm run doc-typecheck && pnpm run verify-cordis-catalog && pnpm run verify-tool-catalog && pnpm run verify-doc-graphs && pnpm run verify-md-wrap && pnpm run verify-md-links && pnpm run verify-doc-refs && pnpm run verify-package-paths && pnpm run verify-mermaid && pnpm run verify-rfc-classification && pnpm run verify-type-equiv", "hygiene": "pnpm run knip && pnpm run publint && pnpm run constraints && pnpm run verify-node-next-types", "demo:echo": "node --expose-internals --import tsx packages/ui/stdio-agent/src/bin.ts examples/echo-agent/cordis.yml", "demo:coding": "node --expose-internals --import tsx packages/ui/stdio-agent/src/bin.ts examples/coding-agent/cordis.yml", @@ -51,15 +52,18 @@ "devDependencies": { "@agentclientprotocol/sdk": "0.25.1", "@stylistic/eslint-plugin": "^5.10.0", + "@types/jsdom": "^28.0.3", "@types/mdast": "^4.0.4", "@types/node": "^25.3.5", "@vitest/coverage-v8": "^4.1.8", "eslint": "^10.4.1", "fast-check": "^4.8.0", + "jsdom": "29.1.1", "knip": "^6.16.1", "lefthook": "^2.1.9", "mdast-util-from-markdown": "^2.0.3", "mdast-util-gfm": "^3.1.0", + "mermaid": "11.16.0", "micromark-extension-gfm": "^3.0.0", "publint": "^0.3.21", "tsdown": "^0.22.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 185ed72b33..66643dcbc3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@stylistic/eslint-plugin': specifier: ^5.10.0 version: 5.10.0(eslint@10.5.0(jiti@2.7.0)) + '@types/jsdom': + specifier: ^28.0.3 + version: 28.0.3 '@types/mdast': specifier: ^4.0.4 version: 4.0.4 @@ -29,6 +32,9 @@ importers: fast-check: specifier: ^4.8.0 version: 4.8.0 + jsdom: + specifier: 29.1.1 + version: 29.1.1 knip: specifier: ^6.16.1 version: 6.16.1 @@ -41,6 +47,9 @@ importers: mdast-util-gfm: specifier: ^3.1.0 version: 3.1.0 + mermaid: + specifier: 11.16.0 + version: 11.16.0 micromark-extension-gfm: specifier: ^3.0.0 version: 3.0.0 @@ -64,7 +73,7 @@ importers: version: 6.1.1(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)) vitest: specifier: ^4.1.8 - version: 4.1.8(@types/node@25.9.3)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)) + version: 4.1.8(@types/node@25.9.3)(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)) packages/bash/bash: devDependencies: @@ -884,6 +893,9 @@ packages: peerDependencies: zod: ^3.25.0 || ^4.0.0 + '@antfu/install-pkg@1.1.0': + resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + '@anthropic-ai/sdk@0.91.1': resolution: {integrity: sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==} hasBin: true @@ -893,6 +905,21 @@ packages: zod: optional: true + '@asamuzakjp/css-color@5.1.11': + resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/dom-selector@7.1.1': + resolution: {integrity: sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/generational-cache@1.0.1': + resolution: {integrity: sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + '@aws-crypto/crc32@5.2.0': resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} engines: {node: '>=16.0.0'} @@ -1044,6 +1071,16 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} + '@braintree/sanitize-url@7.1.2': + resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} + + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true + + '@chevrotain/types@11.1.2': + resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==} + '@cordisjs/plugin-include@1.0.4': resolution: {integrity: sha512-b1Hm1wmue0v7d/jayoXoBjCV2J14XWTL5yyDZEYeL2L9HgcyTq6JbCw99ozSbei98uAbkwq/pBhimsp/HsySeg==} peerDependencies: @@ -1060,6 +1097,42 @@ packages: peerDependencies: cordis: ^4.0.0-rc.5 + '@csstools/color-helpers@6.1.0': + resolution: {integrity: sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.2.1': + resolution: {integrity: sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-color-parser@4.1.9': + resolution: {integrity: sha512-paQcIaOO53Rk5+YrBaBjm/SgrV4INImjo2BT1DtQRYr+XeTRbeAYlS+jxXp9drqvKmtFnWRJKIalDLhZZDu42A==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.6': + resolution: {integrity: sha512-TcJCWFbXLPpJYq6z7bfOyjWYJDiDg2/I4gyUC9pqPNqHFRIey0EB0q0L5cSnQDfWJg8Jd6VadakxdIez/3zkqQ==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} + '@earendil-works/pi-ai@0.79.3': resolution: {integrity: sha512-lMSput/haP5uZAGbXhS5rAYd3GB7GYdJkoAUxg3VFummBeqGqGqllaTWrbHFN12kVGyVfWHhdySNXkiqVh65Iw==} engines: {node: '>=22.19.0'} @@ -1269,6 +1342,15 @@ packages: resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@exodus/bytes@1.15.1': + resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + '@google/genai@1.52.0': resolution: {integrity: sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==} engines: {node: '>=20.0.0'} @@ -1298,6 +1380,12 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@iconify/utils@3.1.3': + resolution: {integrity: sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw==} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -1311,6 +1399,9 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@mermaid-js/parser@1.2.0': + resolution: {integrity: sha512-oYPyv8A4As1yH5Bx+04iQEQxXuIQDe0GKCNSRgao6z8AM9jixXIfP0vsppRLvGf+nKIOb9/LdpWA4YuJiVvESA==} + '@mistralai/mistralai@2.2.1': resolution: {integrity: sha512-uKU8CZmL2RzYKmplsU01hii4p3pe4HqJefpWNRWXm1Tcm0Sm4xXfwSLIy4k7ZCPlbETCGcp69E7hZs+WOJ5itQ==} @@ -1844,6 +1935,99 @@ packages: '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-axis@3.0.6': + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} + + '@types/d3-brush@3.0.6': + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} + + '@types/d3-chord@3.0.6': + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-contour@3.0.6': + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.0': + resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-polygon@3.0.2': + resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + + '@types/d3-random@3.0.3': + resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time-format@4.0.3': + resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + + '@types/d3@7.4.3': + resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + '@types/debug@4.1.13': resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} @@ -1856,6 +2040,12 @@ packages: '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + + '@types/jsdom@28.0.3': + resolution: {integrity: sha512-/HQ2uFoetFTXuye8vzIcHw2z6Fwi7Hi/qcgC+RoS9NCyewiqxhVGqlG+ViGB6lkax481R6dmhf1I7lIGlzJStQ==} + '@types/jsesc@2.5.1': resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} @@ -1877,6 +2067,12 @@ packages: '@types/retry@0.12.0': resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} @@ -1939,6 +2135,9 @@ packages: resolution: {integrity: sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@upsetjs/venn.js@2.0.0': + resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==} + '@vitest/coverage-v8@4.1.8': resolution: {integrity: sha512-lt3kovsyHwYe00wq4D1ti0Z974fWj4NLp6siqiyEufUpyFwK9Yhi7rBhac9JL5aA0zoMrJqc4vYPZRUnI7l7nw==} peerDependencies: @@ -2022,6 +2221,9 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + bignumber.js@9.3.1: resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} @@ -2056,6 +2258,14 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -2071,6 +2281,12 @@ packages: '@cordisjs/plugin-loader': optional: true + cose-base@1.0.3: + resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} + + cose-base@2.2.0: + resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} + cosmokit@1.8.1: resolution: {integrity: sha512-PDBv4l90xZKrUsZ0vtoycgZpO/j4iFsqJXrAxsyBDsnQRI7ZMJXIjgDJsKNjd5L8jnVnnlrDCdhkFbTncgCVjQ==} @@ -2078,10 +2294,177 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + cytoscape-cose-bilkent@4.1.0: + resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape-fcose@2.2.0: + resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape@3.34.0: + resolution: {integrity: sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg==} + engines: {node: '>=0.10'} + + d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + + d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@1.0.9: + resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-sankey@0.12.3: + resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-shape@1.3.7: + resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + d3@7.9.0: + resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} + engines: {node: '>=12'} + + dagre-d3-es@7.0.14: + resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==} + data-uri-to-buffer@4.0.1: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + dayjs@1.11.21: + resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -2091,6 +2474,9 @@ packages: supports-color: optional: true + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + decode-named-character-reference@1.3.0: resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} @@ -2100,6 +2486,9 @@ packages: defu@6.1.7: resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + delaunator@5.1.0: + resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -2111,6 +2500,9 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dompurify@3.4.11: + resolution: {integrity: sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==} + dts-resolver@3.0.0: resolution: {integrity: sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q==} engines: {node: ^22.18.0 || >=24.0.0} @@ -2127,9 +2519,16 @@ packages: resolution: {integrity: sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==} engines: {node: '>=14'} + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} + es-module-lexer@2.1.0: resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + es-toolkit@1.49.0: + resolution: {integrity: sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g==} + esbuild@0.28.1: resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} engines: {node: '>=18'} @@ -2298,6 +2697,9 @@ packages: resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==} engines: {node: '>=14'} + hachure-fill@0.5.2: + resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -2305,6 +2707,10 @@ packages: hookable@6.1.1: resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==} + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -2316,6 +2722,10 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -2324,6 +2734,9 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + import-without-cache@0.4.0: resolution: {integrity: sha512-NkJQA7oZ4YHQhd2+H3BoRFKF3d/XNsiKpHZCQEMH9pDX27hQQLsTyOocyRgaIVtf8gHX3Nt3LPkR4e5EdtPAGQ==} engines: {node: ^22.18.0 || >=24.0.0} @@ -2332,6 +2745,13 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -2340,6 +2760,9 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -2369,6 +2792,15 @@ packages: resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} hasBin: true + jsdom@29.1.1: + resolution: {integrity: sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -2396,14 +2828,27 @@ packages: jws@4.0.1: resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} + katex@0.16.47: + resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==} + hasBin: true + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + khroma@2.1.0: + resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + knip@6.16.1: resolution: {integrity: sha512-TKMn1rxgH6h9vXR9Y0B+Cq7AdPTr9EI02IwoT65NzqYUkvoDQAaJ/aPybiFpAhZ1px6cNYYwXf86iHkBgzCo9w==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + layout-base@1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} + + layout-base@2.0.1: + resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} + lefthook-darwin-arm64@2.1.9: resolution: {integrity: sha512-119HryNcvr4nqn0wUIrNPgpMEPn9yMQzEcW/lezRsnb56PCJriJB92+MCySPVcWDxJnZef7o0T3jdnPNiSH7Qg==} cpu: [arm64] @@ -2540,12 +2985,19 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + lru-cache@11.5.1: + resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} + engines: {node: 20 || >=22} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -2559,6 +3011,11 @@ packages: markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + marked@16.4.2: + resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==} + engines: {node: '>= 20'} + hasBin: true + mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} @@ -2592,6 +3049,12 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + + mermaid@11.16.0: + resolution: {integrity: sha512-Zvm3kbstgdpvIJPPItlL7fppIZ3kibvc1oZIGxdvk9t6UFz6flv+Jw7FtRGKwfcI8OckmH04LqG6LlS6X4B1pA==} + micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -2746,9 +3209,15 @@ packages: package-manager-detector@1.6.0: resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + partial-json@0.1.7: resolution: {integrity: sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==} + path-data-parser@0.1.0: + resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2771,6 +3240,12 @@ packages: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} + points-on-curve@0.2.0: + resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} + + points-on-path@0.2.1: + resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} + postcss@8.5.15: resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} engines: {node: ^10 || ^12 || >=14} @@ -2802,6 +3277,10 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -2809,6 +3288,9 @@ packages: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} + robust-predicates@3.0.3: + resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} + rolldown-plugin-dts@0.25.2: resolution: {integrity: sha512-nMhN/R+vmR8GM45ZW1FWMSjRTSDDn/6w4GTf8RNrEFCBdl8B1kySWrU1ixPtbwzXoRlcO+R/S88VgXuJQwfdDg==} engines: {node: ^22.18.0 || >=24.0.0} @@ -2838,6 +3320,12 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + roughjs@4.6.6: + resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} + + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + sade@1.8.1: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} @@ -2845,6 +3333,13 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + schemastery@3.18.0: resolution: {integrity: sha512-Jw2uxjoyyqc/yeurmChUEc/jbi8GsrdXV/KmqRUDZXJAXAmrJiPsz8vKa17l/VckyzljHZ9oGaul443CQiXxtA==} @@ -2885,6 +3380,9 @@ packages: strnum@2.4.0: resolution: {integrity: sha512-sHrVyWWdq28RbhjuJdZsA1SnGRJV6NiXbk6AXBxDOsgAcA+lmpUZCYjOdLBxkXMwis6RRe7dlZt4VlIWFVzkmg==} + stylis@4.4.0: + resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -2893,6 +3391,9 @@ packages: resolution: {integrity: sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==} engines: {node: '>=12'} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -2908,6 +3409,21 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} + tldts-core@7.4.5: + resolution: {integrity: sha512-pGrwzZDvPwKe+7NNUqAunb6rqTfynr0VOUhCMdqbu5xlvNiszsAJygRzwvpVycdzejlbpY+SWJOn+s75Og7FEA==} + + tldts@7.4.5: + resolution: {integrity: sha512-RfEzKWcq5fHUOFq7J3rl3Oz6ylKGtcHqUznzj4EcXsxLSIjJcvpbXAQtWGeJQ0xKnimR5e0Cn+cn9TssfMzm+g==} + hasBin: true + + tough-cookie@6.0.1: + resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} + engines: {node: '>=16'} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -2921,6 +3437,10 @@ packages: peerDependencies: typescript: '>=4.8.4' + ts-dedent@2.3.0: + resolution: {integrity: sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==} + engines: {node: '>=6.10'} + tsconfck@3.1.6: resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} engines: {node: ^18 || >=20} @@ -3003,6 +3523,10 @@ packages: undici-types@7.24.6: resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + undici@7.28.0: + resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==} + engines: {node: '>=20.18.1'} + unist-util-is@6.0.1: resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} @@ -3018,6 +3542,10 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + uuid@14.0.1: + resolution: {integrity: sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==} + hasBin: true + vite-tsconfig-paths@6.1.1: resolution: {integrity: sha512-2cihq7zliibCCZ8P9cKJrQBkfgdvcFkOOc3Y02o3GWUDLgqjWsZudaoiuOwO/gzTzy17cS5F7ZPo4bsnS4DGkg==} peerDependencies: @@ -3107,6 +3635,10 @@ packages: jsdom: optional: true + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + walk-up-path@4.0.0: resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==} engines: {node: 20 || >=22} @@ -3115,6 +3647,18 @@ packages: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + + whatwg-url@16.0.1: + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -3141,10 +3685,17 @@ packages: utf-8-validate: optional: true + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + xml-naming@0.1.0: resolution: {integrity: sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==} engines: {node: '>=16.0.0'} + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + yaml@2.9.0: resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} engines: {node: '>= 14.6'} @@ -3171,12 +3722,37 @@ snapshots: dependencies: zod: 4.4.3 + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.6.0 + tinyexec: 1.2.4 + '@anthropic-ai/sdk@0.91.1(zod@4.4.3)': dependencies: json-schema-to-ts: 3.1.1 optionalDependencies: zod: 4.4.3 + '@asamuzakjp/css-color@5.1.11': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.9(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@asamuzakjp/dom-selector@7.1.1': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + + '@asamuzakjp/generational-cache@1.0.1': {} + + '@asamuzakjp/nwsapi@2.3.9': {} + '@aws-crypto/crc32@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 @@ -3445,6 +4021,14 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} + '@braintree/sanitize-url@7.1.2': {} + + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.2.1 + + '@chevrotain/types@11.1.2': {} + '@cordisjs/plugin-include@1.0.4(@cordisjs/plugin-loader@1.0.0-rc.4)(cordis@4.0.0-rc.6)': dependencies: '@cordisjs/plugin-loader': 1.0.0-rc.4(cordis@4.0.0-rc.6) @@ -3462,6 +4046,30 @@ snapshots: cordis: 4.0.0-rc.6(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.4) cosmokit: 1.8.1 + '@csstools/color-helpers@6.1.0': {} + + '@csstools/css-calc@3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-color-parser@4.1.9(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.1.0 + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.6(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.0': {} + '@earendil-works/pi-ai@0.79.3(ws@8.21.0)(zod@4.4.3)': dependencies: '@anthropic-ai/sdk': 0.91.1(zod@4.4.3) @@ -3622,6 +4230,8 @@ snapshots: '@eslint/core': 1.2.1 levn: 0.4.1 + '@exodus/bytes@1.15.1': {} + '@google/genai@1.52.0': dependencies: google-auth-library: 10.7.0 @@ -3649,6 +4259,14 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@iconify/types@2.0.0': {} + + '@iconify/utils@3.1.3': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@iconify/types': 2.0.0 + import-meta-resolve: 4.2.0 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -3663,6 +4281,10 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@mermaid-js/parser@1.2.0': + dependencies: + '@chevrotain/types': 11.1.2 + '@mistralai/mistralai@2.2.1': dependencies: ws: 8.21.0 @@ -4021,6 +4643,123 @@ snapshots: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 + '@types/d3-array@3.2.2': {} + + '@types/d3-axis@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-brush@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-chord@3.0.6': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-contour@3.0.6': + dependencies: + '@types/d3-array': 3.2.2 + '@types/geojson': 7946.0.16 + + '@types/d3-delaunay@6.0.4': {} + + '@types/d3-dispatch@3.0.7': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-dsv@3.0.7': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + + '@types/d3-force@3.0.10': {} + + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.0': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-polygon@3.0.2': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.3': {} + + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time-format@4.0.3': {} + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/d3@7.4.3': + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.7 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.0 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.3 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-selection': 3.0.11 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + '@types/debug@4.1.13': dependencies: '@types/ms': 2.1.0 @@ -4031,6 +4770,15 @@ snapshots: '@types/estree@1.0.9': {} + '@types/geojson@7946.0.16': {} + + '@types/jsdom@28.0.3': + dependencies: + '@types/node': 25.9.3 + '@types/tough-cookie': 4.0.5 + parse5: 8.0.1 + undici-types: 7.24.6 + '@types/jsesc@2.5.1': {} '@types/json-schema@7.0.15': {} @@ -4049,6 +4797,11 @@ snapshots: '@types/retry@0.12.0': {} + '@types/tough-cookie@4.0.5': {} + + '@types/trusted-types@2.0.7': + optional: true + '@types/unist@3.0.3': {} '@typescript-eslint/eslint-plugin@8.61.0(@typescript-eslint/parser@8.61.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)': @@ -4142,6 +4895,11 @@ snapshots: '@typescript-eslint/types': 8.61.0 eslint-visitor-keys: 5.0.1 + '@upsetjs/venn.js@2.0.0': + optionalDependencies: + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + '@vitest/coverage-v8@4.1.8(vitest@4.1.8)': dependencies: '@bcoe/v8-coverage': 1.0.2 @@ -4154,7 +4912,7 @@ snapshots: obug: 2.1.3 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.8(@types/node@25.9.3)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)) + vitest: 4.1.8(@types/node@25.9.3)(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)) '@vitest/expect@4.1.8': dependencies: @@ -4236,6 +4994,10 @@ snapshots: base64-js@1.5.1: {} + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + bignumber.js@9.3.1: {} birpc@4.0.0: {} @@ -4260,6 +5022,10 @@ snapshots: dependencies: readdirp: 4.1.2 + commander@7.2.0: {} + + commander@8.3.0: {} + convert-source-map@2.0.0: {} cordis@4.0.0-rc.6(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.4): @@ -4278,6 +5044,14 @@ snapshots: '@cordisjs/plugin-include': link:vendor/include '@cordisjs/plugin-loader': link:vendor/loader + cose-base@1.0.3: + dependencies: + layout-base: 1.0.2 + + cose-base@2.2.0: + dependencies: + layout-base: 2.0.1 + cosmokit@1.8.1: {} cross-spawn@7.0.6: @@ -4286,12 +5060,212 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + + cytoscape-cose-bilkent@4.1.0(cytoscape@3.34.0): + dependencies: + cose-base: 1.0.3 + cytoscape: 3.34.0 + + cytoscape-fcose@2.2.0(cytoscape@3.34.0): + dependencies: + cose-base: 2.2.0 + cytoscape: 3.34.0 + + cytoscape@3.34.0: {} + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + + d3-color@3.1.0: {} + + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.1.0 + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + + d3-ease@3.0.1: {} + + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.2: {} + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@1.0.9: {} + + d3-path@3.1.0: {} + + d3-polygon@3.0.1: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-sankey@0.12.3: + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-selection@3.0.0: {} + + d3-shape@1.3.7: + dependencies: + d3-path: 1.0.9 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3@7.9.0: + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.2 + d3-geo: 3.1.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + + dagre-d3-es@7.0.14: + dependencies: + d3: 7.9.0 + lodash-es: 4.18.1 + data-uri-to-buffer@4.0.1: {} + data-urls@7.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + dayjs@1.11.21: {} + debug@4.4.3: dependencies: ms: 2.1.3 + decimal.js@10.6.0: {} + decode-named-character-reference@1.3.0: dependencies: character-entities: 2.0.2 @@ -4300,6 +5274,10 @@ snapshots: defu@6.1.7: {} + delaunator@5.1.0: + dependencies: + robust-predicates: 3.0.3 + dequal@2.0.3: {} detect-libc@2.1.2: {} @@ -4308,6 +5286,10 @@ snapshots: dependencies: dequal: 2.0.3 + dompurify@3.4.11: + optionalDependencies: + '@types/trusted-types': 2.0.7 + dts-resolver@3.0.0(oxc-resolver@11.20.0): optionalDependencies: oxc-resolver: 11.20.0 @@ -4318,8 +5300,12 @@ snapshots: empathic@2.0.1: {} + entities@8.0.0: {} + es-module-lexer@2.1.0: {} + es-toolkit@1.49.0: {} + esbuild@0.28.1: optionalDependencies: '@esbuild/aix-ppc64': 0.28.1 @@ -4540,10 +5526,18 @@ snapshots: google-logging-utils@1.1.3: {} + hachure-fill@0.5.2: {} + has-flag@4.0.0: {} hookable@6.1.1: {} + html-encoding-sniffer@6.0.0: + dependencies: + '@exodus/bytes': 1.15.1 + transitivePeerDependencies: + - '@noble/hashes' + html-escaper@2.0.2: {} http-proxy-agent@7.0.2: @@ -4560,20 +5554,32 @@ snapshots: transitivePeerDependencies: - supports-color + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.2: {} ignore@7.0.5: {} + import-meta-resolve@4.2.0: {} + import-without-cache@0.4.0: {} imurmurhash@0.1.4: {} + internmap@1.0.1: {} + + internmap@2.0.3: {} + is-extglob@2.1.1: {} is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + is-potential-custom-element-name@1.0.1: {} + isexe@2.0.0: {} istanbul-lib-coverage@3.2.2: {} @@ -4599,6 +5605,32 @@ snapshots: dependencies: argparse: 2.0.1 + jsdom@29.1.1: + dependencies: + '@asamuzakjp/css-color': 5.1.11 + '@asamuzakjp/dom-selector': 7.1.1 + '@bramus/specificity': 2.4.2 + '@csstools/css-syntax-patches-for-csstree': 1.1.6(css-tree@3.2.1) + '@exodus/bytes': 1.15.1 + css-tree: 3.2.1 + data-urls: 7.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.1 + parse5: 8.0.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.1 + undici: 7.28.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + jsesc@3.1.0: {} json-bigint@1.0.0: @@ -4627,10 +5659,16 @@ snapshots: jwa: 2.0.1 safe-buffer: 5.2.1 + katex@0.16.47: + dependencies: + commander: 8.3.0 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 + khroma@2.1.0: {} + knip@6.16.1: dependencies: fdir: 6.5.0(picomatch@4.0.4) @@ -4647,6 +5685,10 @@ snapshots: yaml: 2.9.0 zod: 4.4.3 + layout-base@1.0.2: {} + + layout-base@2.0.1: {} + lefthook-darwin-arm64@2.1.9: optional: true @@ -4748,10 +5790,14 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash-es@4.18.1: {} + long@5.3.2: {} longest-streak@3.1.0: {} + lru-cache@11.5.1: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -4768,6 +5814,8 @@ snapshots: markdown-table@3.0.4: {} + marked@16.4.2: {} + mdast-util-find-and-replace@3.0.2: dependencies: '@types/mdast': 4.0.4 @@ -4870,6 +5918,32 @@ snapshots: dependencies: '@types/mdast': 4.0.4 + mdn-data@2.27.1: {} + + mermaid@11.16.0: + dependencies: + '@braintree/sanitize-url': 7.1.2 + '@iconify/utils': 3.1.3 + '@mermaid-js/parser': 1.2.0 + '@types/d3': 7.4.3 + '@upsetjs/venn.js': 2.0.0 + cytoscape: 3.34.0 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.34.0) + cytoscape-fcose: 2.2.0(cytoscape@3.34.0) + d3: 7.9.0 + d3-sankey: 0.12.3 + dagre-d3-es: 7.0.14 + dayjs: 1.11.21 + dompurify: 3.4.11 + es-toolkit: 1.49.0 + katex: 0.16.47 + khroma: 2.1.0 + marked: 16.4.2 + roughjs: 4.6.6 + stylis: 4.4.0 + ts-dedent: 2.3.0 + uuid: 14.0.1 + micromark-core-commonmark@2.0.3: dependencies: decode-named-character-reference: 1.3.0 @@ -5159,8 +6233,14 @@ snapshots: package-manager-detector@1.6.0: {} + parse5@8.0.1: + dependencies: + entities: 8.0.0 + partial-json@0.1.7: {} + path-data-parser@0.1.0: {} + path-exists@4.0.0: {} path-expression-matcher@1.5.0: {} @@ -5173,6 +6253,13 @@ snapshots: picomatch@4.0.4: {} + points-on-curve@0.2.0: {} + + points-on-path@0.2.1: + dependencies: + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + postcss@8.5.15: dependencies: nanoid: 3.3.12 @@ -5210,10 +6297,14 @@ snapshots: readdirp@4.1.2: {} + require-from-string@2.0.2: {} + resolve-pkg-maps@1.0.0: {} retry@0.13.1: {} + robust-predicates@3.0.3: {} + rolldown-plugin-dts@0.25.2(oxc-resolver@11.20.0)(rolldown@1.1.1)(typescript@6.0.3): dependencies: '@babel/generator': 8.0.0-rc.6 @@ -5272,12 +6363,27 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.1.1 '@rolldown/binding-win32-x64-msvc': 1.1.1 + roughjs@4.6.6: + dependencies: + hachure-fill: 0.5.2 + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + points-on-path: 0.2.1 + + rw@1.3.3: {} + sade@1.8.1: dependencies: mri: 1.2.0 safe-buffer@5.2.1: {} + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + schemastery@3.18.0: dependencies: '@standard-schema/spec': 1.1.0 @@ -5307,12 +6413,16 @@ snapshots: dependencies: anynum: 1.0.0 + stylis@4.4.0: {} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 supports-color@9.4.0: {} + symbol-tree@3.2.4: {} + tinybench@2.9.0: {} tinyexec@1.2.4: {} @@ -5324,6 +6434,20 @@ snapshots: tinyrainbow@3.1.0: {} + tldts-core@7.4.5: {} + + tldts@7.4.5: + dependencies: + tldts-core: 7.4.5 + + tough-cookie@6.0.1: + dependencies: + tldts: 7.4.5 + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + tree-kill@1.2.2: {} ts-algebra@2.0.0: {} @@ -5332,6 +6456,8 @@ snapshots: dependencies: typescript: 6.0.3 + ts-dedent@2.3.0: {} + tsconfck@3.1.6(typescript@6.0.3): optionalDependencies: typescript: 6.0.3 @@ -5399,6 +6525,8 @@ snapshots: undici-types@7.24.6: {} + undici@7.28.0: {} + unist-util-is@6.0.1: dependencies: '@types/unist': 3.0.3 @@ -5422,6 +6550,8 @@ snapshots: dependencies: punycode: 2.3.1 + uuid@14.0.1: {} + vite-tsconfig-paths@6.1.1(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)): dependencies: debug: 4.4.3 @@ -5447,7 +6577,7 @@ snapshots: tsx: 4.22.4 yaml: 2.9.0 - vitest@4.1.8(@types/node@25.9.3)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)): + vitest@4.1.8(@types/node@25.9.3)(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.8 '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)) @@ -5472,13 +6602,30 @@ snapshots: optionalDependencies: '@types/node': 25.9.3 '@vitest/coverage-v8': 4.1.8(vitest@4.1.8) + jsdom: 29.1.1 transitivePeerDependencies: - msw + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + walk-up-path@4.0.0: {} web-streams-polyfill@3.3.3: {} + webidl-conversions@8.0.1: {} + + whatwg-mimetype@5.0.0: {} + + whatwg-url@16.0.1: + dependencies: + '@exodus/bytes': 1.15.1 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + which@2.0.2: dependencies: isexe: 2.0.0 @@ -5492,8 +6639,12 @@ snapshots: ws@8.21.0: {} + xml-name-validator@5.0.0: {} + xml-naming@0.1.0: {} + xmlchars@2.2.0: {} + yaml@2.9.0: {} yocto-queue@0.1.0: {} diff --git a/scripts/gen-doc-graphs.ts b/scripts/gen-doc-graphs.ts index 38a385fa62..cac4c0b2f9 100644 --- a/scripts/gen-doc-graphs.ts +++ b/scripts/gen-doc-graphs.ts @@ -598,31 +598,31 @@ function renderLifecycle(): string { 'sequenceDiagram', ' participant User', ' participant Agent', - ' participant Loop', + ' participant Driver', ' participant Prompt as ctx.systemPrompt', ' participant LLM as ctx.llm', ' participant Tools as ctx.tools', ' participant Session', ' participant Persistence', ' User->>Agent: send(content)', - ' Agent->>Loop: queued work wakes driver', - ' Loop->>Session: turn/start + user/message', - ' Loop-->>User: agent/turn-start', - ' Loop->>Prompt: system-prompt/assemble waterfall', - ' Loop-->>Loop: agent/pre-step serial checkpoint', - ' Loop->>Session: step/start', - ' Loop->>LLM: agent/request waterfall, then llm/stream waterfall', - ' LLM-->>Loop: StreamChunk*', - ' Loop->>Session: assistant/chunk*', - ' Loop-->>User: agent/stream-chunk* (master live mirror)', - ' Loop->>Session: assistant/message', - ' Loop->>Tools: tools/execute waterfall for each tool-call', + ' Agent->>Driver: queued work wakes driver', + ' Driver->>Session: turn/start + user/message', + ' Driver-->>User: agent/turn-start', + ' Driver->>Prompt: system-prompt/assemble waterfall', + ' Driver-->>Driver: agent/pre-step serial checkpoint', + ' Driver->>Session: step/start', + ' Driver->>LLM: agent/request waterfall, then llm/stream waterfall', + ' LLM-->>Driver: StreamChunk*', + ' Driver->>Session: assistant/chunk*', + ' Driver-->>User: agent/stream-chunk* (master live mirror)', + ' Driver->>Session: assistant/message', + ' Driver->>Tools: tools/execute waterfall for each tool-call', ' Tools-->>Session: tool-owned events when applicable', - ' Loop->>Session: tool/result', - ' Loop-->>Loop: agent/turn-continuation waterfall', - ' Loop->>Session: turn/end', - ' Loop->>Persistence: session/flush parallel checkpoint', - ' Loop-->>User: agent/status idle', + ' Driver->>Session: tool/result', + ' Driver-->>Driver: agent/turn-continuation waterfall', + ' Driver->>Session: turn/end', + ' Driver->>Persistence: session/flush parallel checkpoint', + ' Driver-->>User: agent/status idle', '```', '', 'Future pressure from the hooks stack: PR #129 removes the live `agent/stream-chunk` mirror and leaves durable `assistant/chunk` on `session/event` as the authoritative token stream. Consumers that need replayable transcript data should already treat `session/event` as the load-bearing path.', @@ -638,21 +638,21 @@ function renderToolPipeline(): string { '```mermaid', 'flowchart TD', ' model["Assistant message contains tool-call block"]', - ' call["Session event: tool/call"]', + ' toolCall["Session event: tool/call"]', ' waterfall["ctx.tools.execute()
tools/execute waterfall"]', ' policy["Policy / permission / hooks listener"]', - ' body["Registered tool execute() body"]', + ' toolBody["Registered tool execute() body"]', ' owned["Tool-owned session events
todo/write, future fs policy facts"]', - ' result["Session event: tool/result"]', + ' toolResult["Session event: tool/result"]', ' ui["UI presentation
presentCall / presentResult"]', - ' model --> call --> waterfall', + ' model --> toolCall --> waterfall', ' waterfall --> policy', - ' policy -->|next()| body', - ' policy -->|veto / throw| result', - ' body --> owned', - ' body --> result', - ' call --> ui', - ' result --> ui', + ' policy -->|next| toolBody', + ' policy -->|veto / throw| toolResult', + ' toolBody --> owned', + ' toolBody --> toolResult', + ' toolCall --> ui', + ' toolResult --> ui', '```', '', 'Future pressure from the fs stack: PR #128 snapshots a policy rejection card. The graph keeps the veto path explicit because filesystem read-before-edit checks, permission prompts, and hook bridges all belong on this path.', diff --git a/scripts/verify-mermaid.ts b/scripts/verify-mermaid.ts new file mode 100644 index 0000000000..ee27bbeee3 --- /dev/null +++ b/scripts/verify-mermaid.ts @@ -0,0 +1,107 @@ +/** + * Doc-sync gate: verify every fenced ```mermaid block parses with Mermaid's + * own parser. Markdown link/type/code gates can say a diagram block exists and + * is linked, but only Mermaid can catch syntax errors that GitHub would fail to + * render. + * + * Scope matches the Markdown link gate so any Mermaid diagram in repo-authored + * docs is checked: README.md, docs/** /*.md, packages/* /*.md, + * packages/* /* /*.md, examples/** /*.md, AGENTS.md, packages/AGENTS.md, and + * .agents/skills/** /*.md. + * + * Run: `tsx scripts/verify-mermaid.ts`. + */ + +import { readFileSync, realpathSync } from 'node:fs' +import { resolve } from 'node:path' +import { glob } from 'node:fs/promises' +import { fromMarkdown } from 'mdast-util-from-markdown' +import { gfmFromMarkdown } from 'mdast-util-gfm' +import { gfm } from 'micromark-extension-gfm' +import { JSDOM } from 'jsdom' +import type { Nodes } from 'mdast' + +const root = resolve(import.meta.dirname, '..') + +const PATTERNS = [ + 'README.md', + 'docs/**/*.md', + 'packages/*/*.md', + 'packages/*/*/*.md', + 'examples/**/*.md', + 'AGENTS.md', + 'packages/AGENTS.md', + '.agents/skills/**/*.md', +] + +interface Block { + file: string + line: number + source: string +} + +interface Violation { + file: string + line: number + message: string +} + +function extractMermaidBlocks(file: string): Block[] { + const source = readFileSync(resolve(root, file), 'utf8') + const tree = fromMarkdown(source, { extensions: [gfm()], mdastExtensions: [gfmFromMarkdown()] }) + const out: Block[] = [] + const visit = (node: Nodes): void => { + if (node.type === 'code' && node.lang === 'mermaid') { + out.push({ file, line: node.position?.start.line ?? 0, source: node.value }) + } + if ('children' in node) { + for (const child of node.children) visit(child) + } + } + visit(tree) + return out +} + +function formatError(error: unknown): string { + if (error instanceof Error) return error.message.replace(/\s+/g, ' ').trim() + return String(error).replace(/\s+/g, ' ').trim() +} + +const blocks: Block[] = [] +const seen = new Set() +let checkedFiles = 0 +for (const pattern of PATTERNS) { + for await (const match of glob(pattern, { cwd: root })) { + const real = realpathSync(resolve(root, match)) + if (seen.has(real)) continue + seen.add(real) + checkedFiles++ + blocks.push(...extractMermaidBlocks(match)) + } +} + +const violations: Violation[] = [] +const { window } = new JSDOM('') +Object.defineProperty(globalThis, 'window', { value: window }) +Object.defineProperty(globalThis, 'document', { value: window.document }) +Object.defineProperty(globalThis, 'navigator', { value: window.navigator }) +const mermaid = (await import('mermaid')).default +mermaid.initialize({ startOnLoad: false }) +for (const block of blocks) { + try { + await mermaid.parse(block.source, { suppressErrors: false }) + } catch (error: unknown) { + violations.push({ file: block.file, line: block.line, message: formatError(error) }) + } +} + +if (violations.length === 0) { + console.log(`verify-mermaid: ${blocks.length} mermaid block(s) parsed across ${checkedFiles} file(s).`) + process.exit(0) +} + +console.error('verify-mermaid: Mermaid syntax errors found:') +for (const violation of violations) { + console.error(` ${violation.file}:${violation.line} ${violation.message}`) +} +process.exit(1) From fc6b7ccf1e9842616ddf7429615c960e685cd2aa Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sat, 4 Jul 2026 22:43:14 +0800 Subject: [PATCH 03/14] docs: refresh graph atlas after master merge --- docs/graphs/event-producer-consumer.md | 8 ++++---- docs/graphs/package-topology.md | 9 ++------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/docs/graphs/event-producer-consumer.md b/docs/graphs/event-producer-consumer.md index a46e9bf0dc..885086534b 100644 --- a/docs/graphs/event-producer-consumer.md +++ b/docs/graphs/event-producer-consumer.md @@ -9,15 +9,15 @@ This matrix shows which packages dispatch each harness-owned event and which pac | Event | Mode | Declared in | Dispatchers | Listeners | | --- | --- | --- | --- | --- | -| `agent/created` | `emit` | [`packages/core/agent/src/types.ts:234`](../../packages/core/agent/src/types.ts) | [`agent`](../../packages/core/agent) (`emit`) | [`ui-stdio`](../../packages/support/ui-stdio) | -| `agent/disposed` | `emit` | [`packages/core/agent/src/types.ts:241`](../../packages/core/agent/src/types.ts) | [`agent`](../../packages/core/agent) (`emit`) | [`ui-stdio`](../../packages/support/ui-stdio) | +| `agent/created` | `emit` | [`packages/core/agent/src/types.ts:234`](../../packages/core/agent/src/types.ts) | [`agent`](../../packages/core/agent) (`emit`) | [`stdio-agent`](../../packages/ui/stdio-agent) | +| `agent/disposed` | `emit` | [`packages/core/agent/src/types.ts:241`](../../packages/core/agent/src/types.ts) | [`agent`](../../packages/core/agent) (`emit`) | [`stdio-agent`](../../packages/ui/stdio-agent) | | `agent/error` | `emit` | [`packages/core/agent/src/types.ts:389`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`emit`) | - | | `agent/pre-step` | `serial` | [`packages/core/agent/src/types.ts:319`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`serial`) | [`compact-basic`](../../packages/compact/compact-basic) | | `agent/prompt-submit` | `waterfall` | [`packages/core/agent/src/types.ts:332`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`waterfall`) | [`hooks-claude`](../../packages/hooks/hooks-claude), [`hooks-codex`](../../packages/hooks/hooks-codex) | | `agent/queued` | `emit` | [`packages/core/agent/src/types.ts:259`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`emit`) | - | | `agent/request` | `waterfall` | [`packages/core/agent/src/types.ts:345`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`waterfall`), [`compact-basic`](../../packages/compact/compact-basic) (`waterfall`) | - | | `agent/session-start` | `emit` | [`packages/core/agent/src/types.ts:274`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`emit`) | [`hooks-claude`](../../packages/hooks/hooks-claude), [`hooks-codex`](../../packages/hooks/hooks-codex) | -| `agent/status` | `emit` | [`packages/core/agent/src/types.ts:250`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`emit`) | [`acp`](../../packages/ui/acp), [`invariants`](../../packages/support/invariants), [`ui-stdio`](../../packages/support/ui-stdio) | +| `agent/status` | `emit` | [`packages/core/agent/src/types.ts:250`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`emit`) | [`acp`](../../packages/ui/acp), [`invariants`](../../packages/support/invariants), [`stdio-agent`](../../packages/ui/stdio-agent) | | `agent/steering` | `emit` | [`packages/core/agent/src/types.ts:379`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`emit`) | - | | `agent/step-result` | `waterfall` | [`packages/core/agent/src/types.ts:355`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`waterfall`) | - | | `agent/turn-continuation` | `waterfall` | [`packages/core/agent/src/types.ts:368`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`waterfall`) | [`hooks-claude`](../../packages/hooks/hooks-claude), [`hooks-codex`](../../packages/hooks/hooks-codex) | @@ -26,7 +26,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `fs/write-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:109`](../../packages/fs/fs/src/index.ts) | [`tool-fs`](../../packages/fs/tool-fs) (`waterfall`) | [`fs-policy`](../../packages/fs/fs-policy) | | `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:32`](../../packages/llm/llm/src/index.ts) | [`llm`](../../packages/llm/llm) (`waterfall`) | [`llm-replay`](../../packages/support/llm-replay) | | `session/created` | `emit` | [`packages/core/session/src/index.ts:36`](../../packages/core/session/src/index.ts) | [`session`](../../packages/core/session) (`emit`) | [`invariants`](../../packages/support/invariants), [`session-persistence`](../../packages/session-persistence/session-persistence) | -| `session/event` | `emit` | [`packages/core/session/src/index.ts:44`](../../packages/core/session/src/index.ts) | [`session`](../../packages/core/session) (`emit`) | [`acp`](../../packages/ui/acp), [`invariants`](../../packages/support/invariants), [`session-persistence`](../../packages/session-persistence/session-persistence), [`ui-stdio`](../../packages/support/ui-stdio) | +| `session/event` | `emit` | [`packages/core/session/src/index.ts:44`](../../packages/core/session/src/index.ts) | [`session`](../../packages/core/session) (`emit`) | [`acp`](../../packages/ui/acp), [`invariants`](../../packages/support/invariants), [`session-persistence`](../../packages/session-persistence/session-persistence), [`stdio-agent`](../../packages/ui/stdio-agent) | | `session/flush` | `parallel` | [`packages/core/session/src/index.ts:54`](../../packages/core/session/src/index.ts) | [`agent-loop`](../../packages/core/agent-loop) (`parallel`) | [`session-persistence`](../../packages/session-persistence/session-persistence) | | `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:77`](../../packages/subagent/subagent/src/index.ts) | [`subagent`](../../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../../packages/hooks/hooks-claude) | | `subagent/start` | `emit` | [`packages/subagent/subagent/src/index.ts:70`](../../packages/subagent/subagent/src/index.ts) | [`subagent`](../../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../../packages/hooks/hooks-claude) | diff --git a/docs/graphs/package-topology.md b/docs/graphs/package-topology.md index b22cd74f1b..04fadd1438 100644 --- a/docs/graphs/package-topology.md +++ b/docs/graphs/package-topology.md @@ -73,7 +73,6 @@ flowchart TD pkg_invariants["invariants"] pkg_llm_replay["llm-replay"] pkg_subagent_mock["subagent-mock"] - pkg_ui_stdio["ui-stdio"] end subgraph group_ui["packages/ui"] pkg_acp["acp"] @@ -121,9 +120,6 @@ flowchart TD pkg_invariants --> pkg_agent pkg_invariants --> pkg_llm pkg_invariants --> pkg_session - pkg_ui_stdio --> pkg_agent - pkg_ui_stdio --> pkg_llm - pkg_ui_stdio --> pkg_session pkg_agent_loop --> pkg_agent pkg_agent_loop --> pkg_llm pkg_agent_loop --> pkg_session @@ -198,9 +194,9 @@ flowchart TD pkg_acp_agent --> pkg_session_persistence_jsonl pkg_stdio_agent --> pkg_agent pkg_stdio_agent --> pkg_agent_core + pkg_stdio_agent --> pkg_llm pkg_stdio_agent --> pkg_session pkg_stdio_agent --> pkg_session_persistence_jsonl - pkg_stdio_agent --> pkg_ui_stdio ``` | Package | Group | Depends on | @@ -231,7 +227,6 @@ flowchart TD | [`session-persistence-jsonl`](../../packages/session-persistence/session-persistence-jsonl) | `session-persistence` | [`session`](../../packages/core/session), [`session-persistence`](../../packages/session-persistence/session-persistence) | | [`session-persistence-sqlite`](../../packages/session-persistence/session-persistence-sqlite) | `session-persistence` | [`session`](../../packages/core/session), [`session-persistence`](../../packages/session-persistence/session-persistence) | | [`invariants`](../../packages/support/invariants) | `support` | [`agent`](../../packages/core/agent), [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session) | -| [`ui-stdio`](../../packages/support/ui-stdio) | `support` | [`agent`](../../packages/core/agent), [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session) | | [`agent-loop`](../../packages/core/agent-loop) | `core` | [`agent`](../../packages/core/agent), [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session), [`session-persistence`](../../packages/session-persistence/session-persistence), [`system-prompt`](../../packages/core/system-prompt), [`tools`](../../packages/core/tools) | | [`tool-bash`](../../packages/bash/tool-bash) | `bash` | [`agent`](../../packages/core/agent), [`bash`](../../packages/bash/bash), [`llm`](../../packages/llm/llm), [`tools`](../../packages/core/tools) | | [`tool-fs`](../../packages/fs/tool-fs) | `fs` | [`fs`](../../packages/fs/fs), [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session), [`system-prompt`](../../packages/core/system-prompt), [`tools`](../../packages/core/tools) | @@ -249,4 +244,4 @@ flowchart TD | [`subagent-fork`](../../packages/subagent/subagent-fork) | `subagent` | [`agent`](../../packages/core/agent), [`session`](../../packages/core/session), [`subagent`](../../packages/subagent/subagent), [`subagent-inprocess`](../../packages/subagent/subagent-inprocess) | | [`subagent-spawn`](../../packages/subagent/subagent-spawn) | `subagent` | [`subagent`](../../packages/subagent/subagent), [`subagent-inprocess`](../../packages/subagent/subagent-inprocess) | | [`acp-agent`](../../packages/ui/acp-agent) | `ui` | [`acp`](../../packages/ui/acp), [`agent-core`](../../packages/core/agent-core), [`session-persistence-jsonl`](../../packages/session-persistence/session-persistence-jsonl) | -| [`stdio-agent`](../../packages/ui/stdio-agent) | `ui` | [`agent`](../../packages/core/agent), [`agent-core`](../../packages/core/agent-core), [`session`](../../packages/core/session), [`session-persistence-jsonl`](../../packages/session-persistence/session-persistence-jsonl), [`ui-stdio`](../../packages/support/ui-stdio) | +| [`stdio-agent`](../../packages/ui/stdio-agent) | `ui` | [`agent`](../../packages/core/agent), [`agent-core`](../../packages/core/agent-core), [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session), [`session-persistence-jsonl`](../../packages/session-persistence/session-persistence-jsonl) | From 25e02ae9a8774040f98d589644edaaeed9e71429 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 5 Jul 2026 00:45:06 +0800 Subject: [PATCH 04/14] Split the cordis catalog into separate events and services documents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gen-cordis-catalog.ts now emits docs/cordis-catalog/events.md and docs/cordis-catalog/services.md instead of the combined events-and-services.md: a reader is either finding what to listen to or what to call, and each axis now scans and deep-links as its own page. Headings promote one level (scopes and ctx. entries become H2), the dispatch-mode legend lives on the events page, and the inherited tier splits accordingly. --check verifies both files and names whichever is stale. Every reference updated in the same change (no compat redirects, pre-release stance): architecture.md, AGENTS.md, docs/AGENTS.md tier row, filesystem/subagent core-data-structures pages (the ctx.fs anchor survives — slugs are heading-level-independent), fs README, four RFCs, the tool-catalog and persistence-catalog generator intros (both regenerated), and the bilingual development.md pair (re-recorded). --- AGENTS.md | 2 +- docs/AGENTS.md | 2 +- docs/architecture.md | 6 +- .../{events-and-services.md => events.md} | 313 +++--------------- docs/cordis-catalog/services.md | 243 ++++++++++++++ docs/core-data-structures/filesystem.md | 4 +- docs/core-data-structures/subagent.md | 2 +- docs/development.i18n.yaml | 4 +- docs/development.md | 4 +- docs/development.zh.md | 4 +- docs/persistence-catalog/log-events.md | 2 +- .../2026-06-30-event-domain-semantics.md | 2 +- .../2026-06-11-doc-sync-enforcement.md | 2 +- .../2026-06-20-generated-cordis-catalog.md | 2 +- .../process/2026-07-02-tool-schema-catalog.md | 2 +- docs/tool-catalog/tools.md | 2 +- packages/fs/fs/README.md | 2 +- scripts/gen-cordis-catalog.ts | 158 +++++---- scripts/gen-persistence-catalog.ts | 2 +- scripts/gen-tool-catalog.ts | 2 +- 20 files changed, 398 insertions(+), 362 deletions(-) rename docs/cordis-catalog/{events-and-services.md => events.md} (50%) create mode 100644 docs/cordis-catalog/services.md diff --git a/AGENTS.md b/AGENTS.md index c8a1cd226d..ef3526d6fe 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -83,7 +83,7 @@ Real-API tests and demos read `DEEPSEEK_API_KEY` (and optional `DEEPSEEK_BASE_UR - Every npm package is `@deepseek-ai/dsh-`; vendored packages keep upstream names and are `private: true`. `cordis` is a peerDependency (+ dev) of every harness package. - ESM everywhere (`"type": "module"`). Cross-package imports use package names, never relative paths; in-package relative imports use explicit `.ts` extensions. Dev/test/demo run unbuilt via tsx + the root tsconfig `paths` map; building is only for consumers outside the repo. - **Registrations are effects**: every contribution goes through `ctx.effect()` / `ctx.on()`; a registry's `register()` returns the disposer. -- **Typed events via declaration merging**; extensible unions use the merge-extensible-map pattern (`ContentBlockMap`, `SessionEventMap`, …). Every new event's JSDoc carries an `@mode` tag and a `@param` per payload parameter (`this`/trailing `next` exempt); every public service-class method documents each parameter and non-void return (`@param`/`@returns`) — the catalog generator hard-errors otherwise ([completeness RFC](docs/rfc/implemented/process/2026-07-04-cordis-jsdoc-completeness-gate.md)); mode semantics are in the [generated catalog](docs/cordis-catalog/events-and-services.md) header and [the catalog RFC](docs/rfc/implemented/process/2026-06-20-generated-cordis-catalog.md). +- **Typed events via declaration merging**; extensible unions use the merge-extensible-map pattern (`ContentBlockMap`, `SessionEventMap`, …). Every new event's JSDoc carries an `@mode` tag and a `@param` per payload parameter (`this`/trailing `next` exempt); every public service-class method documents each parameter and non-void return (`@param`/`@returns`) — the catalog generator hard-errors otherwise ([completeness RFC](docs/rfc/implemented/process/2026-07-04-cordis-jsdoc-completeness-gate.md)); mode semantics are in the [generated events catalog](docs/cordis-catalog/events.md) header and [the catalog RFC](docs/rfc/implemented/process/2026-06-20-generated-cordis-catalog.md). - **Discriminated unions: `switch` on the tag**, not if-chains. Closed unions end with `default: assertNever(...)`; merge-extensible unions must NOT — handle known cases and fall through `default` with a comment. - **Waterfall listeners MUST call `next()`** to delegate; returning without it is the veto ([semantics](docs/architecture.md#cordis-waterfall-semantics-important)). - **Plugins, not loop changes**: new behavior goes on the documented extension seams; changing `agent-loop` requires updating docs/architecture.md. diff --git a/docs/AGENTS.md b/docs/AGENTS.md index b1df4a02d9..effb771727 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -17,7 +17,7 @@ Every fact has exactly one home — the tier whose job it is — and every other | [cookbook/](cookbook/adding-a-package.md) | Step-by-step how-tos with numbered verify steps | Design rationale (→ the RFC each guide links) | | Package README | The per-package contract: config, semantics, limitations, extension points | JSDoc restatement, generated-catalog restatement (event/tool tables), other packages' concerns | | [development.md](development.md) | Human-facing setup and daily workflow; a bilingual pair under the [i18n contract](i18n/README.md) | Gate-by-gate enumerations that drift from `package.json` scripts | -| Generated catalogs: [cordis-catalog](cordis-catalog/events-and-services.md), [tool-catalog](tool-catalog/tools.md), [persistence-catalog](persistence-catalog/log-events.md), [module-graph.md](module-graph.md) | Exhaustive enumerations regenerated from source, freshness-gated | Hand edits of any kind | +| Generated catalogs: [cordis events](cordis-catalog/events.md), [cordis services](cordis-catalog/services.md), [tool-catalog](tool-catalog/tools.md), [persistence-catalog](persistence-catalog/log-events.md), [module-graph.md](module-graph.md) | Exhaustive enumerations regenerated from source, freshness-gated | Hand edits of any kind | | Skills (`.agents/skills/`) | Workflows: how to carry out a recurring task against the contracts | The contracts themselves (→ docs) | Placement test: a story about a bug → postmortem. Why we chose X → RFC. How to do task Y → cookbook. What type Z looks like → core-data-structures. What package P promises → its README. A rule every agent must always obey → root AGENTS.md, one line, linking the home that holds the why. diff --git a/docs/architecture.md b/docs/architecture.md index 44302fb726..f1f44033a1 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -2,7 +2,7 @@ This document describes the architecture of the DeepSeek Harness — the foundation of **DeepSeek Code**. The governing principle, from the [microkernel design discussion][microkernel-doc]: **everything is a plugin**. The core is deliberately tiny — a handful of abstract services plus one concrete loop plugin (`dsh-agent-loop`) — and every product feature is a plugin against the extension surface described here, without modifying the loop. -This document covers **behavior**; type shapes live in [core-data-structures/](core-data-structures/core.md), the per-event/service reference in the [generated catalog](cordis-catalog/events-and-services.md), per-package contracts in the package READMEs ([map](../packages/README.md)). Requirement context: [Coding Harness MVP 需求分析][mvp-doc]. +This document covers **behavior**; type shapes live in [core-data-structures/](core-data-structures/core.md), the per-event/service reference in the generated [events](cordis-catalog/events.md) / [services](cordis-catalog/services.md) catalogs, per-package contracts in the package READMEs ([map](../packages/README.md)). Requirement context: [Coding Harness MVP 需求分析][mvp-doc]. [microkernel-doc]: https://trtgsjkv6r.feishu.cn/wiki/VS9Lw1kQki6mDJk2UHocyuphnsc [mvp-doc]: https://trtgsjkv6r.feishu.cn/wiki/ZwK6wfBE9i91V6kzMGYcgRGanxg @@ -44,7 +44,7 @@ Dependency rule: extension plugins depend on interfaces, never on `dsh-agent-loo | `ctx.web` | dsh-web | search/fetch provider registries + `WebError` taxonomy | | `ctx.subagents` | dsh-subagent | named provider registry for delegating to child agents | -All registrations go through `ctx.effect()` and return disposers, so hot-reload and fiber disposal clean up automatically (full service interfaces: the [generated catalog](cordis-catalog/events-and-services.md) `## Services` section). +All registrations go through `ctx.effect()` and return disposers, so hot-reload and fiber disposal clean up automatically (full service interfaces: the generated [services catalog](cordis-catalog/services.md)). ## Capability seams: interface / implementation / consumer @@ -140,7 +140,7 @@ A turn ends with one `TurnEndReason` — `completed`, `aborted`, `error`, `dispo ### Event taxonomy -The `agent/*` events are declared in `dsh-agent` (so nothing depends on the loop package); each other service declares its own (`tools/*`, `llm/*`, `system-prompt/*`, `session/*`). The full catalog — signatures, dispatch modes, prose — is generated from source and freshness-gated: [cordis-catalog/events-and-services.md](cordis-catalog/events-and-services.md). Domain semantics (session = the fact log, agent = the live surface): [the event-domain RFC](rfc/implemented/architecture/2026-06-30-event-domain-semantics.md). +The `agent/*` events are declared in `dsh-agent` (so nothing depends on the loop package); each other service declares its own (`tools/*`, `llm/*`, `system-prompt/*`, `session/*`). The full catalog — signatures, dispatch modes, prose — is generated from source and freshness-gated: [cordis-catalog/events.md](cordis-catalog/events.md). Domain semantics (session = the fact log, agent = the live surface): [the event-domain RFC](rfc/implemented/architecture/2026-06-30-event-domain-semantics.md). ### Cordis waterfall semantics (important) diff --git a/docs/cordis-catalog/events-and-services.md b/docs/cordis-catalog/events.md similarity index 50% rename from docs/cordis-catalog/events-and-services.md rename to docs/cordis-catalog/events.md index 81e4ddcd95..d4d8a971a2 100644 --- a/docs/cordis-catalog/events-and-services.md +++ b/docs/cordis-catalog/events.md @@ -1,21 +1,19 @@ -# Cordis Events & Services Catalog +# Cordis Events Catalog -An index reference to the **wiring** a plugin author works against: every cordis event you can listen to (exact signature + dispatch mode) and every `ctx.` service you can call (exact public interface). It complements [core-data-structures/](../core-data-structures/core.md), which catalogs the *data structures* these signatures move around — this page is the verbs, that page is the nouns. +Every cordis event a plugin can listen to: exact signature, dispatch mode, and the declaration's JSDoc. This is one axis of the **wiring** reference a plugin author works against — the callable `ctx.` surface is the sibling [services catalog](services.md), and [core-data-structures/](../core-data-structures/core.md) catalogs the *data structures* these signatures move around. This file is GENERATED from source (`scripts/gen-cordis-catalog.ts`) and verified fresh by `pnpm run verify-cordis-catalog` (part of `doc-sync`) — do not edit it by hand. Signature blocks use a `ts cordis-catalog` fence (skipped by doc-typecheck, since a bare signature is not standalone-compilable). Type names in a signature link to the page that documents them. -The **harness tier** below (the `@deepseek-ai/dsh-*` packages) is the vocabulary this repo owns. The **inherited tier** at the end is the cordis-core + loader/hmr/timer surface a plugin also sees — pinned vendor source, summarized tersely. - -## Events +The **harness tier** below (the `@deepseek-ai/dsh-*` packages) is the vocabulary this repo owns, grouped by scope. The **inherited tier** at the end is the cordis-core + loader/hmr/timer event surface a plugin also sees — pinned vendor source, summarized tersely. Dispatch modes: **emit** (fire-and-forget), **waterfall** (each listener gets `next()` and may transform or veto — see [waterfall semantics](../architecture.md#cordis-waterfall-semantics-important)), **parallel** (awaited fan-out; all listeners run), **serial** (awaited in registration order until one returns a bail value — anything other than `null`, `false`, or `undefined`). -### `agent/*` +## `agent/*` -#### `agent/created` — emit +### `agent/created` — emit An agent was registered in the AgentRegistry and is ready to receive messages. @@ -27,7 +25,7 @@ Types: [Agent](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:234`](../../packages/core/agent/src/types.ts) -#### `agent/disposed` — emit +### `agent/disposed` — emit An agent was disposed and removed from the registry; its fiber and any in-flight turn have been torn down. @@ -39,7 +37,7 @@ Types: [Agent](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:241`](../../packages/core/agent/src/types.ts) -#### `agent/error` — emit +### `agent/error` — emit A step or turn errored. The loop reports a failure here (plus the logger) even when the error has no in-turn position for a session `error` event. @@ -51,7 +49,7 @@ Types: [Agent](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:380`](../../packages/core/agent/src/types.ts) -#### `agent/pre-step` — serial +### `agent/pre-step` — serial Awaited pre-step surface-mutation checkpoint, fired once per step AFTER `turn/start` (and after the prior step closed) but BEFORE this step's `step/start` — so anything a listener appends lands OUTSIDE the step, between `turn/start`/`step/end` and the upcoming `step/start`. `step` is the number of the step about to start. The loop awaits `ctx.serial('agent/pre-step', …)` after assembling the system prompt, then opens the step and derives the request history ONCE from whatever the surface now holds. This is where compaction belongs: it mutates the session surface in place (shadowing an older range with a summary node) with its log-only `compact/*` records cleanly outside any step, and the single subsequent derive reflects the mutation — so there is no double-derive and no listener can see (or be expected to act on) an assembled `messages` array that does not exist yet. @@ -65,7 +63,7 @@ Types: [Agent](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:319`](../../packages/core/agent/src/types.ts) -#### `agent/prompt-submit` — waterfall +### `agent/prompt-submit` — waterfall Waterfall: decide what happens to ONE drained queued message before it becomes a `user/message` — allow (optionally rewriting the prompt bytes or attaching `additionalContext`) or block it. Fires inside the already-open turn, per drained message. Maps onto Claude Code's `UserPromptSubmit` hook. Call `next()` to delegate to the default (allow unchanged), or return a PromptDecision without calling `next()` to short-circuit. @@ -77,7 +75,7 @@ Types: [Agent](../core-data-structures/core.md) · [ContentBlock](../core-data-s Source: [`packages/core/agent/src/types.ts:332`](../../packages/core/agent/src/types.ts) -#### `agent/queued` — emit +### `agent/queued` — emit A message entered the agent's inbox (queued or steering). `source` is the resolved source (defaults applied), not the caller's raw options. @@ -89,7 +87,7 @@ Types: [Agent](../core-data-structures/core.md) · [ContentBlock](../core-data-s Source: [`packages/core/agent/src/types.ts:259`](../../packages/core/agent/src/types.ts) -#### `agent/request` — waterfall +### `agent/request` — waterfall Waterfall: mutate the fully-assembled GenerateOptions before the model call (hooks, model switching, tool filtering, …). Call `next()` to delegate, or return without it to short-circuit. For surface mutation that must precede history derivation (compaction), use agent/pre-step instead — by the time this fires, `options.messages` is already derived. @@ -101,7 +99,7 @@ Types: [Agent](../core-data-structures/core.md) · [GenerateOptions](../core-dat Source: [`packages/core/agent/src/types.ts:345`](../../packages/core/agent/src/types.ts) -#### `agent/session-start` — emit +### `agent/session-start` — emit The agent's session lifecycle began, fired once before its first turn. `source` says why (SessionStartSource: fresh startup, a resumed persisted session, …). A pure NOTIFICATION (emit, not waterfall): it carries no veto — a session-start listener that wants to seed context does so via `agent.inject()` (a `context/message` the first request sees), not by returning a decision. Cannot block the session from starting; that gap is deliberate (a bridge logs/injects, it does not gate startup). @@ -113,7 +111,7 @@ Types: [Agent](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:274`](../../packages/core/agent/src/types.ts) -#### `agent/status` — emit +### `agent/status` — emit Agent status changed (`idle` ⇄ `running`, or → `disposed`). Drive lifecycle off this transition, never off a status you just requested — `send()` does not flip status to `running` before it returns. @@ -125,7 +123,7 @@ Types: [Agent](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:250`](../../packages/core/agent/src/types.ts) -#### `agent/step-result` — waterfall +### `agent/step-result` — waterfall Waterfall: post-process the assembled assistant Message before tool dispatch (validation, content rewriting, …). @@ -137,7 +135,7 @@ Types: [Agent](../core-data-structures/core.md) · [Message](../core-data-struct Source: [`packages/core/agent/src/types.ts:355`](../../packages/core/agent/src/types.ts) -#### `agent/turn-continuation` — waterfall +### `agent/turn-continuation` — waterfall Waterfall: override the turn-continuation decision via a typed ContinuationDecision. The loop's `defaultDecision` is `continue` when the step had tool calls or steering was injected, else `stop`. Listeners force-continue (`/goal`, `/loop` — optionally attaching a `reason` recorded as next-step steering) or force-stop (budget guards). Call `next()` to delegate to the default, or return a decision to override. @@ -149,9 +147,9 @@ Types: [Agent](../core-data-structures/core.md) Source: [`packages/core/agent/src/types.ts:368`](../../packages/core/agent/src/types.ts) -### `fs/*` +## `fs/*` -#### `fs/edit-intent` — waterfall +### `fs/edit-intent` — waterfall Single-slot decision: produce the optional version guard for the next FileSystem.editText. The tool dispatches this as an unbound waterfall and supplies a default thunk returning `undefined` (unconditional edit of the current content — the bare provider; no `stat`). The `@deepseek-ai/dsh-fs-policy` policy listener returns `{ version: vObserved }`, or throws `FS_NOT_OBSERVED` if the actor is unset or has not observed the target. Does NOT call `next()`: one decision, first-wins (see Events.'fs/write-intent'). @@ -163,7 +161,7 @@ Types: [FsTarget](../core-data-structures/filesystem.md) · [FsVersion](../core- Source: [`packages/fs/fs/src/index.ts:123`](../../packages/fs/fs/src/index.ts) -#### `fs/observed` — emit +### `fs/observed` — emit Record that an actor observed a target at a version, after a successful read/write/edit. Fire-and-forget (plain `emit`). A listener MUST be a synchronous, side-effect-only recorder (`@deepseek-ai/dsh-fs-policy`'s is a `WeakMap.set`): the tool does not guard the emit, so a listener that throws surfaces as the tool's `isError` result, and cordis `emit` does not await listener promises — async or fallible audit/telemetry does not belong here. No listener ⇒ nothing recorded. `actor` is the opaque tool-execution context. @@ -175,7 +173,7 @@ Types: [FsTarget](../core-data-structures/filesystem.md) · [FsVersion](../core- Source: [`packages/fs/fs/src/index.ts:138`](../../packages/fs/fs/src/index.ts) -#### `fs/write-intent` — waterfall +### `fs/write-intent` — waterfall Single-slot decision: produce the write intent for the next FileSystem.writeText. The tool dispatches this as an unbound waterfall (no `this`) and supplies a default thunk returning `undefined` (unconditional create-or-overwrite — the bare provider). The `@deepseek-ai/dsh-fs-policy` policy listener returns `createIfAbsent` (unobserved actor) or `{ kind: 'replaceIfVersion', version: vObserved }` (observed) and does NOT call `next()` — one decision, not a composable chain. The slot is first-wins: the first non-`next()` decider (registration order, or `prepend`) occupies it; a second decider is a misconfiguration, not layering. `actor` is the opaque tool-execution context, never read here. @@ -187,9 +185,9 @@ Types: [FsTarget](../core-data-structures/filesystem.md) · [FsWriteIntent](../c Source: [`packages/fs/fs/src/index.ts:109`](../../packages/fs/fs/src/index.ts) -### `llm/*` +## `llm/*` -#### `llm/stream` — waterfall +### `llm/stream` — waterfall Waterfall around every streaming model call (retry, caching, routing). Bound to the LlmService; call `next()` to reach the resolved adapter's stream, or yield your own chunks to short-circuit. @@ -201,9 +199,9 @@ Types: [GenerateOptions](../core-data-structures/core.md) · [StreamChunk](../co Source: [`packages/llm/llm/src/index.ts:32`](../../packages/llm/llm/src/index.ts) -### `session/*` +## `session/*` -#### `session/created` — emit +### `session/created` — emit A session was created in the store. @@ -213,7 +211,7 @@ A session was created in the store. Source: [`packages/core/session/src/index.ts:36`](../../packages/core/session/src/index.ts) -#### `session/event` — emit +### `session/event` — emit An event was appended to a session log (sync, fire-and-forget). This is the per-append feed a UI or invariant plugin tails. @@ -225,7 +223,7 @@ Types: [SessionEvent](../core-data-structures/core.md) Source: [`packages/core/session/src/index.ts:44`](../../packages/core/session/src/index.ts) -#### `session/flush` — parallel +### `session/flush` — parallel Awaited durability checkpoint. The agent loop awaits `ctx.parallel('session/flush', session)` at every turn end; persistence plugins (JSONL, SQLite) drain their write-behind buffers here and on fiber dispose. Awaited (parallel), not a waterfall: every listener runs and the loop waits for all of them, but none can veto. @@ -235,9 +233,9 @@ Awaited durability checkpoint. The agent loop awaits `ctx.parallel('session/flus Source: [`packages/core/session/src/index.ts:54`](../../packages/core/session/src/index.ts) -### `subagent/*` +## `subagent/*` -#### `subagent/end` — emit +### `subagent/end` — emit A subagent run settled — emitted when SubagentRun.result resolves (any stop reason). Paired with Events['subagent/start']. @@ -247,7 +245,7 @@ A subagent run settled — emitted when SubagentRun.result resolves (any stop re Source: [`packages/subagent/subagent/src/index.ts:77`](../../packages/subagent/subagent/src/index.ts) -#### `subagent/start` — emit +### `subagent/start` — emit A subagent run started — emitted after the provider is resolved and its capabilities validated, as the child run begins. Paired with Events['subagent/end']. @@ -257,9 +255,9 @@ A subagent run started — emitted after the provider is resolved and its capabi Source: [`packages/subagent/subagent/src/index.ts:70`](../../packages/subagent/subagent/src/index.ts) -### `system-prompt/*` +## `system-prompt/*` -#### `system-prompt/assemble` — waterfall +### `system-prompt/assemble` — waterfall Waterfall around prompt assembly — mutate or extend the PromptAssembly (sections + tool schemas) before it is rendered. Bound to the SystemPrompt service; call `next()` to delegate. @@ -269,7 +267,7 @@ Waterfall around prompt assembly — mutate or extend the PromptAssembly (sectio Source: [`packages/core/system-prompt/src/index.ts:26`](../../packages/core/system-prompt/src/index.ts) -#### `system-prompt/change` — emit +### `system-prompt/change` — emit A section or tool provider was registered or unregistered (the assembly inputs changed). @@ -279,9 +277,9 @@ A section or tool provider was registered or unregistered (the assembly inputs c Source: [`packages/core/system-prompt/src/index.ts:32`](../../packages/core/system-prompt/src/index.ts) -### `tools/*` +## `tools/*` -#### `tools/change` — emit +### `tools/change` — emit A tool was registered or unregistered (the available tool set changed). @@ -291,7 +289,7 @@ A tool was registered or unregistered (the available tool set changed). Source: [`packages/core/tools/src/index.ts:87`](../../packages/core/tools/src/index.ts) -#### `tools/post-execute` — waterfall +### `tools/post-execute` — waterfall Waterfall AFTER a tool runs — where hook plugins inspect the result and accept it (optionally REPLACING the model-facing content, and/or attaching `additionalContext` for the next request) or block it with corrective `feedback` (Claude Code's `PostToolUse`). Listeners receive `(exec, result, next)`: call `next()` to delegate to the default (accept unchanged), or return a PostToolDecision to override. The core tool dispatch sits between the two waterfalls as plain code, all inside `execute`'s outer try/catch (and the tool body keeps its own inner try/catch, so a thrown tool still reaches `post-execute` as an `isError` result). @@ -303,7 +301,7 @@ Types: [ToolExecution](../core-data-structures/tools.md) · [ToolExecutionResult Source: [`packages/core/tools/src/index.ts:82`](../../packages/core/tools/src/index.ts) -#### `tools/pre-execute` — waterfall +### `tools/pre-execute` — waterfall Waterfall BEFORE a tool runs — the gate where sandbox, permission, and hook plugins allow or deny a call (Claude Code's `PreToolUse`). Listeners receive `(exec, next)`: call `next()` to delegate to the default (allow), or return a PreToolDecision without calling `next()` to short-circuit. A `deny` skips dispatch and yields an `isError` result; the tool body never runs. Input rewrite is deliberately NOT offered here (see PreToolDecision); `ask` degrades to deny until the permission system lands (`FIXME(permissions)`). @@ -315,233 +313,9 @@ Types: [ToolExecution](../core-data-structures/tools.md) Source: [`packages/core/tools/src/index.ts:66`](../../packages/core/tools/src/index.ts) -## Services +## Inherited events (cordis core + loader/hmr/timer) -The `ctx.` services the harness provides. An abstract seam (e.g. `ctx.bash`) is implemented by a separate package; the interface is what consumers code against. - -### `ctx.agentLoop` — `AgentLoop` - -The agent-loop plugin (`ctx.agentLoop`): creates ReactLoopAgents, runs their loops, and registers them in `ctx.agents`. Also implements the AgentFactory seam, so plugins create/resume agents through `ctx.agents` (the interface) without depending on this concrete package. - -The loop itself is deliberately thin — every behavior beyond "call the model, run the tools, repeat" belongs to plugins listening on the event taxonomy declared in @deepseek-ai/dsh-agent. - -```ts cordis-catalog -create(id: AgentId, options: AgentOptions = {}): ReactLoopAgent -createAgent(options: CreateAgentOptions): AgentHandle -async resume(options: ResumeAgentOptions): Promise -``` - -Source: [`packages/core/agent-loop/src/index.ts:63`](../../packages/core/agent-loop/src/index.ts) - -### `ctx.agents` — `AgentRegistry` - -Agent registry (`ctx.agents`): tracks live agents so UI, hook, and orchestrator plugins can find them without depending on the concrete loop package. Agent *creation* is provided by whichever plugin implements the AgentFactory (phase 1: `@deepseek-ai/dsh-agent-loop`), registered via setFactory. - -```ts cordis-catalog -setFactory(factory: AgentFactory): () => void -create(options: CreateAgentOptions): AgentHandle -async resume(options: ResumeAgentOptions): Promise -register(agent: Agent): () => void -get(id: AgentId): Agent | undefined -list(): Agent[] -``` - -Types: [Agent](../core-data-structures/core.md) - -Source: [`packages/core/agent/src/index.ts:117`](../../packages/core/agent/src/index.ts) - -### `ctx.bash` — `BashExecutor` (abstract seam) - -Abstract bash execution service. Subclass, implement the abstract methods, and load the subclass as a plugin — it registers as `ctx.bash` (one implementation per context; loading a second throws, which is cordis' standard duplicate-service behavior). - -Semantics every implementation must honor: - -- run REJECTS only for infrastructure failures (unusable workdir, missing shell, pre-aborted signal). Nonzero exits, timeout kills, and abort kills RESOLVE with a descriptive BashRunResult — reporting a failed command is the tool layer's job, not an exception. -- start returns immediately; no timeout applies to background tasks (callers stop them via kill or the spec's AbortSignal). Completion must fire the onTaskDone listeners exactly once per task, and must NOT fire after the service is disposed. -- readOutput is incremental: consecutive reads never re-deliver output. Implementations bound their buffers; reads that lost data flag `lossy` and point at full-stream spill files when available. -- Disposal kills every running task and awaits their exit (no orphan processes survive `fiber.dispose()`). - -```ts cordis-catalog -abstract resolve(request: BashExecRequest): BashExecSpec -abstract run(spec: BashExecSpec): Promise -abstract start(spec: BashExecSpec): BashTask -abstract get(id: BashTaskId): BashTask | undefined -abstract ownerOf(id: BashTaskId): OwnerToken | undefined -abstract list(): BashTask[] -abstract readOutput(id: BashTaskId): BashTaskRead -abstract kill(id: BashTaskId): boolean -onTaskDone(listener: BashTaskListener): () => void -``` - -Types: [BashExecRequest](../core-data-structures/bash.md) · [BashExecSpec](../core-data-structures/bash.md) · [BashRunResult](../core-data-structures/bash.md) · [BashTask](../core-data-structures/bash.md) · [BashTaskRead](../core-data-structures/bash.md) - -Source: [`packages/bash/bash/src/index.ts:59`](../../packages/bash/bash/src/index.ts) - -### `ctx.compact` — `CompactService` (abstract seam) - -Abstract compaction service. Subclass implement the two abstract methods, and load the subclass as a plugin — it registers as `ctx.compact` (one implementation per context; loading a second throws, which is cordis' standard duplicate-service behavior). - -Both core methods are abstract: the contract states WHAT compaction does, while the entire strategy — token estimation, retention policy, event sequencing, summarization — is a HOW decision owned by the implementation. - -Implementations MUST honor: - -- **Surface contract**: a successful compaction shadows the compacted surface nodes with a SINGLE replacement node carrying the summary. Because `SurfaceEventType` is a closed union, that node is a `user/message` with `surfaceOp: { op:'replace', start, end }`; the `compact/*` events are log-only (lock + provenance). -- **Blocking**: no compaction begins while another is in progress for the same session. The recommended mechanism is the log-recorded lock — append `compact/start` before the slow work and `compact/end` after (even on failure) — so the lock is visible to replay and crash recovery. - -```ts cordis-catalog -abstract compactIfNeeded( agent: CompactAgentContext, turn: number, step: number, fullSystemPrompt: string, signal: AbortSignal, ): Promise -abstract compactRegion( session: Session, start: number, end: number, agent: CompactAgentContext, turn: number, step: number, signal?: AbortSignal, ): Promise -``` - -Source: [`packages/compact/compact/src/index.ts:63`](../../packages/compact/compact/src/index.ts) - -### `ctx.fs` — `FileSystem` (abstract seam) - -Abstract filesystem provider service. Subclass, implement the seven storage primitives, and load the subclass as a plugin — it registers as `ctx.fs` (one implementation per context; loading a second throws, cordis' standard duplicate-service behavior). - -Semantics every backend must honor: - -- resolve returns a stable FsTarget; the same underlying file reached by different input paths must yield the same `targetKey` so stale guards and target lookup agree across paths (e.g. through symlinks). -- stat returns FsInfo metadata (never content) or `undefined` when the target is absent. -- readText/streamText read the whole regular text file (the stream for large files); both own regular-file checks, UTF-8 decoding, binary/NUL rejection, and `FS_NOT_TEXT`. -- listDir returns direct children of a directory in stable name order with resolved child targets and cheap metadata only. It never reads file contents. Missing targets throw `FS_NOT_FOUND`, non-directories throw `FS_NOT_DIRECTORY`, permission failures throw `FS_PERMISSION_DENIED`, and other backend I/O failures throw `FS_IO_ERROR`. -- writeText is atomic temp-file + rename. `expected` is OPTIONAL: omit it for an unconditional create-or-overwrite (the bare-provider default), or supply a FsWriteIntent to guard the write. -- editText verifies `expected.version` BEFORE literal matching (so a stale edit reports `FS_STALE_VERSION`, not `FS_EDIT_NOT_FOUND`/ `FS_AMBIGUOUS_EDIT` against newer content), then applies literal replacement and writes atomically — all inside one mutation critical section. `expected` is OPTIONAL: omit it for an unconditional edit of the current content (a missing target still reports `FS_STALE_VERSION`). - -```ts cordis-catalog -abstract resolve(path: string, opts?: { cwd?: string }): Promise -abstract stat(target: FsTarget, signal?: AbortSignal): Promise -abstract readText(target: FsTarget, signal?: AbortSignal): Promise -abstract streamText(target: FsTarget, signal?: AbortSignal): Promise> -abstract listDir(target: FsTarget, signal?: AbortSignal): Promise -abstract writeText(target: FsTarget, content: string, expected?: FsWriteIntent, signal?: AbortSignal): Promise -abstract editText(target: FsTarget, edit: FsEditRequest, expected?: { version: FsVersion }, signal?: AbortSignal): Promise -``` - -Types: [FsEditOutcome](../core-data-structures/filesystem.md) · [FsEditRequest](../core-data-structures/filesystem.md) · [FsInfo](../core-data-structures/filesystem.md) · [FsTarget](../core-data-structures/filesystem.md) · [FsVersion](../core-data-structures/filesystem.md) · [FsWriteIntent](../core-data-structures/filesystem.md) · [FsWriteOutcome](../core-data-structures/filesystem.md) - -Source: [`packages/fs/fs/src/index.ts:172`](../../packages/fs/fs/src/index.ts) - -### `ctx.llm` — `LlmService` - -The abstract `llm` service: an adapter registry plus a streaming model-call surface, interceptable via the `llm/stream` waterfall. - -```ts cordis-catalog -registerAdapter(models: string[], adapter: LlmAdapter): () => void -models(): string[] -stream(options: GenerateOptions): AsyncIterable -``` - -Types: [GenerateOptions](../core-data-structures/core.md) · [StreamChunk](../core-data-structures/llm-streaming.md) - -Source: [`packages/llm/llm/src/index.ts:70`](../../packages/llm/llm/src/index.ts) - -### `ctx.sessionPersistence` — `SessionPersistence` (abstract seam) - -Abstract durable session-persistence service. Subclass, implement the abstract methods, and load the subclass as a plugin — it registers as `ctx.sessionPersistence` (one implementation per context; loading a second throws, cordis' standard duplicate-service behavior). - -Contracts every implementation MUST honor (a DB backend asserts them inside a transaction; a file backend appends at EOF): - -- **Append-only; a crashed turn is closed, not truncated.** Committed events — those at or below a flushed `turn/end` — are never rewritten. A crash can leave an unclosed final turn whose events are real (and possibly large); load preserves them and closes the orphaned turn with synthetic boundary events (see load). Only a never-fully-written torn tail fragment is discarded. -- **Contiguous seq.** A persisted log is contiguous: `events[i].seq === i`. load rejects a parse error or a `seq` gap in the COMMITTED region (unloadable); append's first event `seq` MUST equal the backend's stored next-seq (after `load` has balanced any interrupted turn). -- **JSON-serializable data.** `SessionEventMap` is merge-extensible and `event.data` is typed only as `SessionEventMap[K]`, so append REJECTS non-JSON-serializable data with an error naming the offending event type. A backend snapshots (serializes/clones) each event when it buffers, since `session.events` hands out the live mutable object. -- **Durability.** append returns only once the batch is durable (the file backend fsyncs; a DB commits). create MAY defer the physical write until the first append (lazy materialization). - -```ts cordis-catalog -abstract create(meta: SessionHeader): Promise -abstract append(id: SessionId, events: readonly SessionEvent[]): Promise -abstract load(id: SessionId): Promise<{ meta: SessionHeader; events: SessionEvent[] }> -abstract list(): Promise -``` - -Types: [SessionEvent](../core-data-structures/core.md) - -Source: [`packages/session-persistence/session-persistence/src/index.ts:98`](../../packages/session-persistence/session-persistence/src/index.ts) - -### `ctx.sessions` — `SessionStore` - -In-memory session store (`ctx.sessions`). - -Persistence is intentionally not implemented here — persistence plugins subscribe to `session/event` and flush on `session/flush` / dispose. - -```ts cordis-catalog -create(id?: SessionId, options?: CreateSessionOptions): Session -prepare(id?: SessionId, options?: CreateSessionOptions): Session -enter(session: Session): () => void -announce(session: Session): void -get(id: SessionId): Session | undefined -list(): Session[] -``` - -Source: [`packages/core/session/src/index.ts:327`](../../packages/core/session/src/index.ts) - -### `ctx.subagents` — `SubagentService` - -The `subagents` service: a registry of named SubagentProviders and a capability-checked start surface. - -```ts cordis-catalog -registerProvider(provider: SubagentProvider): () => void -getProvider(name: string): SubagentProvider | undefined -list(): string[] -start(name: string, request: SubagentStartRequest): SubagentRun -``` - -Source: [`packages/subagent/subagent/src/index.ts:123`](../../packages/subagent/subagent/src/index.ts) - -### `ctx.systemPrompt` — `SystemPrompt` - -Registry service (`ctx.systemPrompt`): plugins contribute ordered text sections and tool-schema providers; the agent loop calls `assemble()` once per step. - -```ts cordis-catalog -section(section: PromptSection): () => void -tools(provider: () => ToolSchema[]): () => void -assemble(): Promise -``` - -Source: [`packages/core/system-prompt/src/index.ts:73`](../../packages/core/system-prompt/src/index.ts) - -### `ctx.tools` — `ToolRegistry` - -Tool registry (`ctx.tools`): tool plugins register definitions; the agent loop executes calls through the `tools/pre-execute` → dispatch → `tools/post-execute` pipeline. The registry contributes its schemas into the system-prompt assembly. - -```ts cordis-catalog -register(definition: ToolDefinition): () => void -get(name: string): ToolDefinition | undefined -schemas(): ToolSchema[] -async execute(exec: ToolExecution): Promise -``` - -Types: [ToolDefinition](../core-data-structures/tools.md) · [ToolExecution](../core-data-structures/tools.md) · [ToolExecutionResult](../core-data-structures/tools.md) - -Source: [`packages/core/tools/src/index.ts:268`](../../packages/core/tools/src/index.ts) - -### `ctx.web` — `WebService` - -The web access service. Registered as `ctx.web` (one instance per context). - -Selection semantics (resolved at execution time, never order-dependent): - -- A configured id that is registered and `status().available` → that provider. -- A configured id not registered → `WEB_PROVIDER_CONFIGURED_MISSING`. -- A configured id registered but unavailable → `WEB_PROVIDER_CONFIGURED_UNAVAILABLE`. -- No id configured, exactly one registered usable provider → that provider. -- No id configured, multiple usable providers → `WEB_PROVIDER_AMBIGUOUS`. -- No id configured, no usable provider → `WEB_PROVIDER_UNAVAILABLE`. - -```ts cordis-catalog -registerSearchProvider(provider: WebSearchProvider): () => void -registerFetchProvider(provider: WebFetchProvider): () => void -async search(request: WebSearchRequest, exec?: WebExecContext): Promise -async fetch(request: WebFetchRequest, exec?: WebExecContext): Promise -``` - -Source: [`packages/web/web/src/index.ts:87`](../../packages/web/web/src/index.ts) - -## Inherited tier (cordis core + loader/hmr/timer) - -The framework surface every plugin inherits, beyond the harness vocabulary above. This is pinned vendor source ([vendoring policy](../../vendor/README.md)); it is summarized here so the catalog is a complete picture of what `ctx` and the event bus offer, without elevating framework internals to the harness tier's prominence. - -### Inherited events +The framework events every plugin also sees, beyond the harness vocabulary above. This is pinned vendor source ([vendoring policy](../../vendor/README.md)); it is summarized here so the page is a complete picture of the event bus, without elevating framework internals to the harness tier's prominence. - `internal/plugin` — A plugin fiber was created. ([`vendor/cordis/src/events.ts:197`](../../vendor/cordis/src/events.ts)) - `internal/status` — A fiber changed lifecycle state. ([`vendor/cordis/src/events.ts:198`](../../vendor/cordis/src/events.ts)) @@ -558,16 +332,3 @@ The framework surface every plugin inherits, beyond the harness vocabulary above - `loader/entry-init` — A config entry is being initialized. ([`vendor/loader/src/index.ts:25`](../../vendor/loader/src/index.ts)) - `loader/partial-dispose` — An entry is being partially disposed on reload. ([`vendor/loader/src/index.ts:26`](../../vendor/loader/src/index.ts)) - `loader/patch-context` — A context is being patched during a reload. ([`vendor/loader/src/index.ts:27`](../../vendor/loader/src/index.ts)) - -### Inherited `ctx` members - -- `ctx.on / ctx.once` — Register an event listener (disposable). ([`vendor/cordis/src/events.ts:29`](../../vendor/cordis/src/events.ts)) -- `ctx.emit / ctx.parallel / ctx.serial / ctx.bail / ctx.waterfall` — Dispatch an event (sync / awaited / first-bail / veto-chain). ([`vendor/cordis/src/events.ts:29`](../../vendor/cordis/src/events.ts)) -- `ctx.plugin / ctx.inject` — Load a plugin / declare required services. ([`vendor/cordis/src/registry.ts:144`](../../vendor/cordis/src/registry.ts)) -- `ctx.effect` — Register a disposable side effect tied to the fiber. ([`vendor/cordis/src/fiber.ts:9`](../../vendor/cordis/src/fiber.ts)) -- `ctx.get / ctx.set / ctx.provide / ctx.accessor / ctx.mixin` — Low-level service-store access and binding. ([`vendor/cordis/src/reflect.ts:7`](../../vendor/cordis/src/reflect.ts)) -- `ctx.extend / ctx.isolate / ctx.intercept` — Derive a child context (scoped services / isolation / interception). ([`vendor/cordis/src/context.ts:35`](../../vendor/cordis/src/context.ts)) -- `ctx.root / ctx.scope / ctx.fiber / ctx.registry / ctx.reflect / ctx.events / ctx.logger` — Ambient handles onto the running context graph. ([`vendor/cordis/src/context.ts:16`](../../vendor/cordis/src/context.ts)) -- `ctx.timer (+ interval / timeout / throttle / debounce / setTimeout / setInterval)` — Disposable timer helpers. The `timer` key is provided at runtime; the six helpers are mixed onto ctx directly (declared via Pick). ([`vendor/timer/src/index.ts:4`](../../vendor/timer/src/index.ts)) -- `ctx.loader` — The config Loader that booted the app (present under the loader). ([`vendor/loader/src/index.ts:30`](../../vendor/loader/src/index.ts)) -- `ctx.hmr` — The hot-module-reload watcher (present under the hmr plugin). ([`vendor/hmr/src/index.ts:15`](../../vendor/hmr/src/index.ts)) diff --git a/docs/cordis-catalog/services.md b/docs/cordis-catalog/services.md new file mode 100644 index 0000000000..76caf139fd --- /dev/null +++ b/docs/cordis-catalog/services.md @@ -0,0 +1,243 @@ + + +# Cordis Services Catalog + +Every `ctx.` service a plugin can call: the exact public interface plus the class JSDoc. This is one axis of the **wiring** reference a plugin author works against — the events a plugin listens to are the sibling [events catalog](events.md), and [core-data-structures/](../core-data-structures/core.md) catalogs the *data structures* these signatures move around. An abstract seam (e.g. `ctx.bash`) is implemented by a separate package; the interface is what consumers code against. + +This file is GENERATED from source (`scripts/gen-cordis-catalog.ts`) and verified fresh by `pnpm run verify-cordis-catalog` (part of `doc-sync`) — do not edit it by hand. Signature blocks use a `ts cordis-catalog` fence (skipped by doc-typecheck, since a bare signature is not standalone-compilable). Type names in a signature link to the page that documents them. + +The **harness tier** below (the `@deepseek-ai/dsh-*` packages) is the vocabulary this repo owns. The **inherited tier** at the end is the cordis-core + loader/hmr/timer `ctx` surface a plugin also sees — pinned vendor source, summarized tersely. + +## `ctx.agentLoop` — `AgentLoop` + +The agent-loop plugin (`ctx.agentLoop`): creates ReactLoopAgents, runs their loops, and registers them in `ctx.agents`. Also implements the AgentFactory seam, so plugins create/resume agents through `ctx.agents` (the interface) without depending on this concrete package. + +The loop itself is deliberately thin — every behavior beyond "call the model, run the tools, repeat" belongs to plugins listening on the event taxonomy declared in @deepseek-ai/dsh-agent. + +```ts cordis-catalog +create(id: AgentId, options: AgentOptions = {}): ReactLoopAgent +createAgent(options: CreateAgentOptions): AgentHandle +async resume(options: ResumeAgentOptions): Promise +``` + +Source: [`packages/core/agent-loop/src/index.ts:63`](../../packages/core/agent-loop/src/index.ts) + +## `ctx.agents` — `AgentRegistry` + +Agent registry (`ctx.agents`): tracks live agents so UI, hook, and orchestrator plugins can find them without depending on the concrete loop package. Agent *creation* is provided by whichever plugin implements the AgentFactory (phase 1: `@deepseek-ai/dsh-agent-loop`), registered via setFactory. + +```ts cordis-catalog +setFactory(factory: AgentFactory): () => void +create(options: CreateAgentOptions): AgentHandle +async resume(options: ResumeAgentOptions): Promise +register(agent: Agent): () => void +get(id: AgentId): Agent | undefined +list(): Agent[] +``` + +Types: [Agent](../core-data-structures/core.md) + +Source: [`packages/core/agent/src/index.ts:117`](../../packages/core/agent/src/index.ts) + +## `ctx.bash` — `BashExecutor` (abstract seam) + +Abstract bash execution service. Subclass, implement the abstract methods, and load the subclass as a plugin — it registers as `ctx.bash` (one implementation per context; loading a second throws, which is cordis' standard duplicate-service behavior). + +Semantics every implementation must honor: + +- run REJECTS only for infrastructure failures (unusable workdir, missing shell, pre-aborted signal). Nonzero exits, timeout kills, and abort kills RESOLVE with a descriptive BashRunResult — reporting a failed command is the tool layer's job, not an exception. +- start returns immediately; no timeout applies to background tasks (callers stop them via kill or the spec's AbortSignal). Completion must fire the onTaskDone listeners exactly once per task, and must NOT fire after the service is disposed. +- readOutput is incremental: consecutive reads never re-deliver output. Implementations bound their buffers; reads that lost data flag `lossy` and point at full-stream spill files when available. +- Disposal kills every running task and awaits their exit (no orphan processes survive `fiber.dispose()`). + +```ts cordis-catalog +abstract resolve(request: BashExecRequest): BashExecSpec +abstract run(spec: BashExecSpec): Promise +abstract start(spec: BashExecSpec): BashTask +abstract get(id: BashTaskId): BashTask | undefined +abstract ownerOf(id: BashTaskId): OwnerToken | undefined +abstract list(): BashTask[] +abstract readOutput(id: BashTaskId): BashTaskRead +abstract kill(id: BashTaskId): boolean +onTaskDone(listener: BashTaskListener): () => void +``` + +Types: [BashExecRequest](../core-data-structures/bash.md) · [BashExecSpec](../core-data-structures/bash.md) · [BashRunResult](../core-data-structures/bash.md) · [BashTask](../core-data-structures/bash.md) · [BashTaskRead](../core-data-structures/bash.md) + +Source: [`packages/bash/bash/src/index.ts:59`](../../packages/bash/bash/src/index.ts) + +## `ctx.compact` — `CompactService` (abstract seam) + +Abstract compaction service. Subclass implement the two abstract methods, and load the subclass as a plugin — it registers as `ctx.compact` (one implementation per context; loading a second throws, which is cordis' standard duplicate-service behavior). + +Both core methods are abstract: the contract states WHAT compaction does, while the entire strategy — token estimation, retention policy, event sequencing, summarization — is a HOW decision owned by the implementation. + +Implementations MUST honor: + +- **Surface contract**: a successful compaction shadows the compacted surface nodes with a SINGLE replacement node carrying the summary. Because `SurfaceEventType` is a closed union, that node is a `user/message` with `surfaceOp: { op:'replace', start, end }`; the `compact/*` events are log-only (lock + provenance). +- **Blocking**: no compaction begins while another is in progress for the same session. The recommended mechanism is the log-recorded lock — append `compact/start` before the slow work and `compact/end` after (even on failure) — so the lock is visible to replay and crash recovery. + +```ts cordis-catalog +abstract compactIfNeeded( agent: CompactAgentContext, turn: number, step: number, fullSystemPrompt: string, signal: AbortSignal, ): Promise +abstract compactRegion( session: Session, start: number, end: number, agent: CompactAgentContext, turn: number, step: number, signal?: AbortSignal, ): Promise +``` + +Source: [`packages/compact/compact/src/index.ts:63`](../../packages/compact/compact/src/index.ts) + +## `ctx.fs` — `FileSystem` (abstract seam) + +Abstract filesystem provider service. Subclass, implement the seven storage primitives, and load the subclass as a plugin — it registers as `ctx.fs` (one implementation per context; loading a second throws, cordis' standard duplicate-service behavior). + +Semantics every backend must honor: + +- resolve returns a stable FsTarget; the same underlying file reached by different input paths must yield the same `targetKey` so stale guards and target lookup agree across paths (e.g. through symlinks). +- stat returns FsInfo metadata (never content) or `undefined` when the target is absent. +- readText/streamText read the whole regular text file (the stream for large files); both own regular-file checks, UTF-8 decoding, binary/NUL rejection, and `FS_NOT_TEXT`. +- listDir returns direct children of a directory in stable name order with resolved child targets and cheap metadata only. It never reads file contents. Missing targets throw `FS_NOT_FOUND`, non-directories throw `FS_NOT_DIRECTORY`, permission failures throw `FS_PERMISSION_DENIED`, and other backend I/O failures throw `FS_IO_ERROR`. +- writeText is atomic temp-file + rename. `expected` is OPTIONAL: omit it for an unconditional create-or-overwrite (the bare-provider default), or supply a FsWriteIntent to guard the write. +- editText verifies `expected.version` BEFORE literal matching (so a stale edit reports `FS_STALE_VERSION`, not `FS_EDIT_NOT_FOUND`/ `FS_AMBIGUOUS_EDIT` against newer content), then applies literal replacement and writes atomically — all inside one mutation critical section. `expected` is OPTIONAL: omit it for an unconditional edit of the current content (a missing target still reports `FS_STALE_VERSION`). + +```ts cordis-catalog +abstract resolve(path: string, opts?: { cwd?: string }): Promise +abstract stat(target: FsTarget, signal?: AbortSignal): Promise +abstract readText(target: FsTarget, signal?: AbortSignal): Promise +abstract streamText(target: FsTarget, signal?: AbortSignal): Promise> +abstract listDir(target: FsTarget, signal?: AbortSignal): Promise +abstract writeText(target: FsTarget, content: string, expected?: FsWriteIntent, signal?: AbortSignal): Promise +abstract editText(target: FsTarget, edit: FsEditRequest, expected?: { version: FsVersion }, signal?: AbortSignal): Promise +``` + +Types: [FsEditOutcome](../core-data-structures/filesystem.md) · [FsEditRequest](../core-data-structures/filesystem.md) · [FsInfo](../core-data-structures/filesystem.md) · [FsTarget](../core-data-structures/filesystem.md) · [FsVersion](../core-data-structures/filesystem.md) · [FsWriteIntent](../core-data-structures/filesystem.md) · [FsWriteOutcome](../core-data-structures/filesystem.md) + +Source: [`packages/fs/fs/src/index.ts:172`](../../packages/fs/fs/src/index.ts) + +## `ctx.llm` — `LlmService` + +The abstract `llm` service: an adapter registry plus a streaming model-call surface, interceptable via the `llm/stream` waterfall. + +```ts cordis-catalog +registerAdapter(models: string[], adapter: LlmAdapter): () => void +models(): string[] +stream(options: GenerateOptions): AsyncIterable +``` + +Types: [GenerateOptions](../core-data-structures/core.md) · [StreamChunk](../core-data-structures/llm-streaming.md) + +Source: [`packages/llm/llm/src/index.ts:70`](../../packages/llm/llm/src/index.ts) + +## `ctx.sessionPersistence` — `SessionPersistence` (abstract seam) + +Abstract durable session-persistence service. Subclass, implement the abstract methods, and load the subclass as a plugin — it registers as `ctx.sessionPersistence` (one implementation per context; loading a second throws, cordis' standard duplicate-service behavior). + +Contracts every implementation MUST honor (a DB backend asserts them inside a transaction; a file backend appends at EOF): + +- **Append-only; a crashed turn is closed, not truncated.** Committed events — those at or below a flushed `turn/end` — are never rewritten. A crash can leave an unclosed final turn whose events are real (and possibly large); load preserves them and closes the orphaned turn with synthetic boundary events (see load). Only a never-fully-written torn tail fragment is discarded. +- **Contiguous seq.** A persisted log is contiguous: `events[i].seq === i`. load rejects a parse error or a `seq` gap in the COMMITTED region (unloadable); append's first event `seq` MUST equal the backend's stored next-seq (after `load` has balanced any interrupted turn). +- **JSON-serializable data.** `SessionEventMap` is merge-extensible and `event.data` is typed only as `SessionEventMap[K]`, so append REJECTS non-JSON-serializable data with an error naming the offending event type. A backend snapshots (serializes/clones) each event when it buffers, since `session.events` hands out the live mutable object. +- **Durability.** append returns only once the batch is durable (the file backend fsyncs; a DB commits). create MAY defer the physical write until the first append (lazy materialization). + +```ts cordis-catalog +abstract create(meta: SessionHeader): Promise +abstract append(id: SessionId, events: readonly SessionEvent[]): Promise +abstract load(id: SessionId): Promise<{ meta: SessionHeader; events: SessionEvent[] }> +abstract list(): Promise +``` + +Types: [SessionEvent](../core-data-structures/core.md) + +Source: [`packages/session-persistence/session-persistence/src/index.ts:98`](../../packages/session-persistence/session-persistence/src/index.ts) + +## `ctx.sessions` — `SessionStore` + +In-memory session store (`ctx.sessions`). + +Persistence is intentionally not implemented here — persistence plugins subscribe to `session/event` and flush on `session/flush` / dispose. + +```ts cordis-catalog +create(id?: SessionId, options?: CreateSessionOptions): Session +prepare(id?: SessionId, options?: CreateSessionOptions): Session +enter(session: Session): () => void +announce(session: Session): void +get(id: SessionId): Session | undefined +list(): Session[] +``` + +Source: [`packages/core/session/src/index.ts:327`](../../packages/core/session/src/index.ts) + +## `ctx.subagents` — `SubagentService` + +The `subagents` service: a registry of named SubagentProviders and a capability-checked start surface. + +```ts cordis-catalog +registerProvider(provider: SubagentProvider): () => void +getProvider(name: string): SubagentProvider | undefined +list(): string[] +start(name: string, request: SubagentStartRequest): SubagentRun +``` + +Source: [`packages/subagent/subagent/src/index.ts:123`](../../packages/subagent/subagent/src/index.ts) + +## `ctx.systemPrompt` — `SystemPrompt` + +Registry service (`ctx.systemPrompt`): plugins contribute ordered text sections and tool-schema providers; the agent loop calls `assemble()` once per step. + +```ts cordis-catalog +section(section: PromptSection): () => void +tools(provider: () => ToolSchema[]): () => void +assemble(): Promise +``` + +Source: [`packages/core/system-prompt/src/index.ts:73`](../../packages/core/system-prompt/src/index.ts) + +## `ctx.tools` — `ToolRegistry` + +Tool registry (`ctx.tools`): tool plugins register definitions; the agent loop executes calls through the `tools/pre-execute` → dispatch → `tools/post-execute` pipeline. The registry contributes its schemas into the system-prompt assembly. + +```ts cordis-catalog +register(definition: ToolDefinition): () => void +get(name: string): ToolDefinition | undefined +schemas(): ToolSchema[] +async execute(exec: ToolExecution): Promise +``` + +Types: [ToolDefinition](../core-data-structures/tools.md) · [ToolExecution](../core-data-structures/tools.md) · [ToolExecutionResult](../core-data-structures/tools.md) + +Source: [`packages/core/tools/src/index.ts:268`](../../packages/core/tools/src/index.ts) + +## `ctx.web` — `WebService` + +The web access service. Registered as `ctx.web` (one instance per context). + +Selection semantics (resolved at execution time, never order-dependent): + +- A configured id that is registered and `status().available` → that provider. +- A configured id not registered → `WEB_PROVIDER_CONFIGURED_MISSING`. +- A configured id registered but unavailable → `WEB_PROVIDER_CONFIGURED_UNAVAILABLE`. +- No id configured, exactly one registered usable provider → that provider. +- No id configured, multiple usable providers → `WEB_PROVIDER_AMBIGUOUS`. +- No id configured, no usable provider → `WEB_PROVIDER_UNAVAILABLE`. + +```ts cordis-catalog +registerSearchProvider(provider: WebSearchProvider): () => void +registerFetchProvider(provider: WebFetchProvider): () => void +async search(request: WebSearchRequest, exec?: WebExecContext): Promise +async fetch(request: WebFetchRequest, exec?: WebExecContext): Promise +``` + +Source: [`packages/web/web/src/index.ts:87`](../../packages/web/web/src/index.ts) + +## Inherited `ctx` members (cordis core + loader/hmr/timer) + +The framework `ctx` surface every plugin also sees, beyond the harness services above. This is pinned vendor source ([vendoring policy](../../vendor/README.md)); it is summarized here so the page is a complete picture of what `ctx` offers, without elevating framework internals to the harness tier's prominence. + +- `ctx.on / ctx.once` — Register an event listener (disposable). ([`vendor/cordis/src/events.ts:29`](../../vendor/cordis/src/events.ts)) +- `ctx.emit / ctx.parallel / ctx.serial / ctx.bail / ctx.waterfall` — Dispatch an event (sync / awaited / first-bail / veto-chain). ([`vendor/cordis/src/events.ts:29`](../../vendor/cordis/src/events.ts)) +- `ctx.plugin / ctx.inject` — Load a plugin / declare required services. ([`vendor/cordis/src/registry.ts:144`](../../vendor/cordis/src/registry.ts)) +- `ctx.effect` — Register a disposable side effect tied to the fiber. ([`vendor/cordis/src/fiber.ts:9`](../../vendor/cordis/src/fiber.ts)) +- `ctx.get / ctx.set / ctx.provide / ctx.accessor / ctx.mixin` — Low-level service-store access and binding. ([`vendor/cordis/src/reflect.ts:7`](../../vendor/cordis/src/reflect.ts)) +- `ctx.extend / ctx.isolate / ctx.intercept` — Derive a child context (scoped services / isolation / interception). ([`vendor/cordis/src/context.ts:35`](../../vendor/cordis/src/context.ts)) +- `ctx.root / ctx.scope / ctx.fiber / ctx.registry / ctx.reflect / ctx.events / ctx.logger` — Ambient handles onto the running context graph. ([`vendor/cordis/src/context.ts:16`](../../vendor/cordis/src/context.ts)) +- `ctx.timer (+ interval / timeout / throttle / debounce / setTimeout / setInterval)` — Disposable timer helpers. The `timer` key is provided at runtime; the six helpers are mixed onto ctx directly (declared via Pick). ([`vendor/timer/src/index.ts:4`](../../vendor/timer/src/index.ts)) +- `ctx.loader` — The config Loader that booted the app (present under the loader). ([`vendor/loader/src/index.ts:30`](../../vendor/loader/src/index.ts)) +- `ctx.hmr` — The hot-module-reload watcher (present under the hmr plugin). ([`vendor/hmr/src/index.ts:15`](../../vendor/hmr/src/index.ts)) diff --git a/docs/core-data-structures/filesystem.md b/docs/core-data-structures/filesystem.md index e13322dc5a..bbc55be966 100644 --- a/docs/core-data-structures/filesystem.md +++ b/docs/core-data-structures/filesystem.md @@ -90,7 +90,7 @@ interface FsEditOutcome { `dsh-fs` owns three events the tool dispatches and the policy plugin listens for, so the emitter (`dsh-tool-fs`) and the listener (`dsh-fs-policy`) share a vocabulary without the emitter depending on the policy plugin. They carry only `dsh-fs` vocabulary plus an opaque `object` actor — no model-facing concepts and no agent/session owner structure. -`fs/write-intent` and `fs/edit-intent` are **single-slot decision waterfalls**: the tool dispatches each with a default thunk returning `undefined` (the bare provider), and a listener fully decides without calling `next()`. The slot is first-wins by registration order — the policy plugin owning it is a deployment convention, not an enforced invariant. `fs/observed` is a fire-and-forget recording event dispatched with a plain `ctx.emit`; its listener MUST be synchronous and side-effect-only, because the tool does NOT guard the emit — a throwing listener would surface as the tool's `isError` result for a mutation that already succeeded. The generated catalog shows the exact signatures on [events-and-services.md](../cordis-catalog/events-and-services.md). +`fs/write-intent` and `fs/edit-intent` are **single-slot decision waterfalls**: the tool dispatches each with a default thunk returning `undefined` (the bare provider), and a listener fully decides without calling `next()`. The slot is first-wins by registration order — the policy plugin owning it is a deployment convention, not an enforced invariant. `fs/observed` is a fire-and-forget recording event dispatched with a plain `ctx.emit`; its listener MUST be synchronous and side-effect-only, because the tool does NOT guard the emit — a throwing listener would surface as the tool's `isError` result for a mutation that already succeeded. The generated catalog shows the exact signatures on [events.md](../cordis-catalog/events.md). ## Execution context (policy plugin) @@ -144,4 +144,4 @@ type FsErrorCode = ## The service and the plugin -`FileSystem` (`ctx.fs`, abstract) owns the provider primitives: `resolve`, `stat`, `readText`, `streamText`, `listDir`, `writeText`, and `editText`. `dsh-fs-policy` registers **no service** — it is a plugin that adds policy through the `fs/*` event gate: it decides the write/edit intent waterfalls (supplying `createIfAbsent`/`replaceIfVersion`/`{ version }` or throwing `FS_NOT_OBSERVED`) and records on `fs/observed`. The executor is `dsh-tool-fs`: it reads/writes/edits through `ctx.fs`, dispatches the waterfalls, and emits the recording event. The generated wiring catalog shows the exact `ctx.fs` signatures on [events-and-services.md](../cordis-catalog/events-and-services.md#ctxfs--filesystem-abstract-seam). +`FileSystem` (`ctx.fs`, abstract) owns the provider primitives: `resolve`, `stat`, `readText`, `streamText`, `listDir`, `writeText`, and `editText`. `dsh-fs-policy` registers **no service** — it is a plugin that adds policy through the `fs/*` event gate: it decides the write/edit intent waterfalls (supplying `createIfAbsent`/`replaceIfVersion`/`{ version }` or throwing `FS_NOT_OBSERVED`) and records on `fs/observed`. The executor is `dsh-tool-fs`: it reads/writes/edits through `ctx.fs`, dispatches the waterfalls, and emits the recording event. The generated wiring catalog shows the exact `ctx.fs` signatures on [services.md](../cordis-catalog/services.md#ctxfs--filesystem-abstract-seam). diff --git a/docs/core-data-structures/subagent.md b/docs/core-data-structures/subagent.md index 1d998b60b7..32b5224773 100644 --- a/docs/core-data-structures/subagent.md +++ b/docs/core-data-structures/subagent.md @@ -85,7 +85,7 @@ interface SubagentProvider { } ``` -The service (`ctx.subagents`) emits `subagent/start` when a run begins and `subagent/end` when it settles (see the [events catalog](../cordis-catalog/events-and-services.md)). `subagent/end` carries `lastAssistantMessage` (the child's final `output`) on the settle path, so an observer sees WHAT the subagent produced without holding the run (absent when the run rejected at the infrastructure level — no result was produced). These are **observe-only** events: both are plain `emit`s (the `subagent/end` fires from a detached `.then` after the result settles and awaits no listener), so a subscriber observes but cannot change the run. Both emits contain a thrown listener **per listener** (logged, never propagated): one bad subscriber can neither strand a live run, surface as an unhandled rejection on the detached settle hook, nor starve the listeners registered after it. +The service (`ctx.subagents`) emits `subagent/start` when a run begins and `subagent/end` when it settles (see the [events catalog](../cordis-catalog/events.md)). `subagent/end` carries `lastAssistantMessage` (the child's final `output`) on the settle path, so an observer sees WHAT the subagent produced without holding the run (absent when the run rejected at the infrastructure level — no result was produced). These are **observe-only** events: both are plain `emit`s (the `subagent/end` fires from a detached `.then` after the result settles and awaits no listener), so a subscriber observes but cannot change the run. Both emits contain a thrown listener **per listener** (logged, never propagated): one bad subscriber can neither strand a live run, surface as an unhandled rejection on the detached settle hook, nor starve the listeners registered after it. ## In-process backends: depth and seed diff --git a/docs/development.i18n.yaml b/docs/development.i18n.yaml index 13c50cb61f..e06b738ca3 100644 --- a/docs/development.i18n.yaml +++ b/docs/development.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -development.md: c57ccd4a393bdf54a13c5786b70b6b665904aedd -development.zh.md: 198b7651e6937b3f5280a6104a5b43b35f40262b +development.md: a797008cefe2da205a0a7b8aa7ab818ea0035dd5 +development.zh.md: 69b597bb40135606c01c36f151a6e0ad817f6cf6 diff --git a/docs/development.md b/docs/development.md index c57ccd4a39..a797008cef 100644 --- a/docs/development.md +++ b/docs/development.md @@ -96,8 +96,8 @@ pnpm run typecheck # build package/vendor outputs, then typecheck examples, pnpm run lint # eslint . pnpm run lint:fix # eslint . --fix pnpm run doc-typecheck # compile checked TypeScript snippets in Markdown docs -pnpm run gen-cordis-catalog # regenerate docs/cordis-catalog/events-and-services.md from source -pnpm run verify-cordis-catalog # fail if the cordis events/services catalog is stale +pnpm run gen-cordis-catalog # regenerate docs/cordis-catalog/events.md + services.md from source +pnpm run verify-cordis-catalog # fail if either cordis catalog is stale pnpm run gen-rfc-index # regenerate the docs/rfc/README.md index tables from the RFC tree pnpm run verify-md-wrap # fail on hard-wrapped prose paragraphs in docs/README markdown pnpm run verify-type-equiv # fail if a ```ts type-equiv doc block drifts from its source type diff --git a/docs/development.zh.md b/docs/development.zh.md index 198b7651e6..69b597bb40 100644 --- a/docs/development.zh.md +++ b/docs/development.zh.md @@ -96,8 +96,8 @@ pnpm run typecheck # build package/vendor outputs, then typecheck examples, pnpm run lint # eslint . pnpm run lint:fix # eslint . --fix pnpm run doc-typecheck # compile checked TypeScript snippets in Markdown docs -pnpm run gen-cordis-catalog # regenerate docs/cordis-catalog/events-and-services.md from source -pnpm run verify-cordis-catalog # fail if the cordis events/services catalog is stale +pnpm run gen-cordis-catalog # regenerate docs/cordis-catalog/events.md + services.md from source +pnpm run verify-cordis-catalog # fail if either cordis catalog is stale pnpm run gen-rfc-index # regenerate the docs/rfc/README.md index tables from the RFC tree pnpm run verify-md-wrap # fail on hard-wrapped prose paragraphs in docs/README markdown pnpm run verify-type-equiv # fail if a ```ts type-equiv doc block drifts from its source type diff --git a/docs/persistence-catalog/log-events.md b/docs/persistence-catalog/log-events.md index 5bca109a76..bce865dedd 100644 --- a/docs/persistence-catalog/log-events.md +++ b/docs/persistence-catalog/log-events.md @@ -3,7 +3,7 @@ # Persistence Log Event Catalog -Every event type that can appear in a session's durable event log: each member of the merge-extensible `SessionEventMap` — the owning vocabulary in `@deepseek-ai/dsh-session` plus every plugin declaration merge in this repo — with the payload it carries, its surface badge, and the declaration it comes from. It complements [session.md](../core-data-structures/session.md) (the `SessionEvent` envelope, surface list, and `deriveMessages()` projection), [persistence.md](../core-data-structures/persistence.md) (how the log is made durable), and the [cordis catalog](../cordis-catalog/events-and-services.md) (the live bus wiring — a log event is NOT a cordis event; it reaches listeners via the single `session/event` emit). +Every event type that can appear in a session's durable event log: each member of the merge-extensible `SessionEventMap` — the owning vocabulary in `@deepseek-ai/dsh-session` plus every plugin declaration merge in this repo — with the payload it carries, its surface badge, and the declaration it comes from. It complements [session.md](../core-data-structures/session.md) (the `SessionEvent` envelope, surface list, and `deriveMessages()` projection), [persistence.md](../core-data-structures/persistence.md) (how the log is made durable), and the [cordis events catalog](../cordis-catalog/events.md) (the live bus wiring — a log event is NOT a cordis event; it reaches listeners via the single `session/event` emit). This file is GENERATED from source (`scripts/gen-persistence-catalog.ts`) and verified fresh by `pnpm run verify-persistence-catalog` (part of `doc-sync`) — do not edit it by hand. Payload blocks use a `ts persistence-catalog` fence (skipped by doc-typecheck, since a bare payload fragment is not standalone-compilable). Type names in a payload link to the page that documents them. See [the persistence-log-catalog RFC](../rfc/implemented/process/2026-07-04-persistence-log-catalog.md). diff --git a/docs/rfc/implemented/architecture/2026-06-30-event-domain-semantics.md b/docs/rfc/implemented/architecture/2026-06-30-event-domain-semantics.md index 9a9db6359f..eff6164dbf 100644 --- a/docs/rfc/implemented/architecture/2026-06-30-event-domain-semantics.md +++ b/docs/rfc/implemented/architecture/2026-06-30-event-domain-semantics.md @@ -32,4 +32,4 @@ This is the foundational change in a stack that adds a Hooks subsystem; it estab - Tests that observed boundaries via the removed emits now observe the durable `turn/start`/`turn/end`/`step/start`/`step/end` session events — the behavior they pin (boundary ordering, step counting) is unchanged; only the feed they read moved to the canonical one. The tests that exercised a *throwing turn-boundary emit listener* were deleted, because that code path no longer exists (there is no emit to throw from). Per [AGENTS.md "tests document behavior, not golden truth"](../../../../AGENTS.md), the behavior and its test moved (or died) together. - The loop marks the step open (`stepOpen = true`) BEFORE appending `step/start`, because `Session.append` pushes the event to the log before notifying `session/event` listeners (validation throws happen earlier, before the push — see [the session append contract](../../../core-data-structures/session.md)). So a throwing `step/start` session-event listener runs with the step already open and the event already in the log: the loop's outer catch then calls `closeStep()`, which appends the balancing `step/end`, and the turn closes balanced with an error (`turn/start → step/start → step/end → turn/end` — verified by the invariants oracle in the regression test). Closing the open step is owed precisely because the marker is set first. - The full realization of this is [the simplification RFC "Stop mirroring durable boundaries as agent events"](../simplification/2026-06-20-remove-agent-boundary-mirror-events.md): all four boundary mirrors are removed and every consumer reads boundaries off `session/event`. `agent/steering` (not a boundary mirror) stayed outside that RFC's scope and was removed by its own follow-up, [Remove the `agent/steering` mirror emit](../simplification/2026-07-04-remove-agent-steering-mirror.md) — it mirrored the durable `steering/message`. -- The cordis catalog (`docs/cordis-catalog/events-and-services.md`) is regenerated to drop the mirror events. +- The cordis events catalog (`docs/cordis-catalog/events.md`) is regenerated to drop the mirror events. diff --git a/docs/rfc/implemented/process/2026-06-11-doc-sync-enforcement.md b/docs/rfc/implemented/process/2026-06-11-doc-sync-enforcement.md index 8278a1d4b7..ba44b61373 100644 --- a/docs/rfc/implemented/process/2026-06-11-doc-sync-enforcement.md +++ b/docs/rfc/implemented/process/2026-06-11-doc-sync-enforcement.md @@ -13,7 +13,7 @@ AGENTS.md promises that docs and code stay strictly in sync, but the promise was Two gates, mirroring the existing `scripts/` style (tsx ESM, one job each): 1. **`doc-typecheck`** extracts every fenced ` ```ts ` block from `README.md`, `docs/**`, and `packages/*/README.md`, writes them to a temp project extending the root `tsconfig.json`, and compiles it with `tsc -b`. The temp project reuses the source `paths` map and the root project references, so documentation examples see source while vendored code remains checked under its own tsconfig settings. A block that is a deliberate sketch opts out with an explicit ` ```ts ignore-check ` info string; the script reports the opt-out ratio and fails if it exceeds half, so the escape hatch can't quietly become the norm. -2. **`verify-event-taxonomy`** extracts the event names from the `interface Events` blocks across `packages/*/src` and from the taxonomy table in `docs/architecture.md`, and asserts the two sets match exactly. Verify, don't generate: the table keeps its hand-written Mode/Purpose columns; only the set of names is checked. (Landing this surfaced three events the table had been missing — `tools/change`, `llm/adapter-change`, `system-prompt/change`.) **Superseded** by [the generated cordis catalog](2026-06-20-generated-cordis-catalog.md): this gate and its `architecture.md` table are retired in favor of a fully-generated `docs/cordis-catalog/events-and-services.md` and its `verify-cordis-catalog` freshness gate. The other gates here (`doc-typecheck`, and the `verify-md-wrap` amendment below) are unaffected. +2. **`verify-event-taxonomy`** extracts the event names from the `interface Events` blocks across `packages/*/src` and from the taxonomy table in `docs/architecture.md`, and asserts the two sets match exactly. Verify, don't generate: the table keeps its hand-written Mode/Purpose columns; only the set of names is checked. (Landing this surfaced three events the table had been missing — `tools/change`, `llm/adapter-change`, `system-prompt/change`.) **Superseded** by [the generated cordis catalog](2026-06-20-generated-cordis-catalog.md): this gate and its `architecture.md` table are retired in favor of the fully-generated `docs/cordis-catalog/events.md` + `docs/cordis-catalog/services.md` and their `verify-cordis-catalog` freshness gate. The other gates here (`doc-typecheck`, and the `verify-md-wrap` amendment below) are unaffected. Both run via a shared `doc-sync` package.json script that the lefthook pre-push hook and CI both invoke ([mechanical quality gates](2026-06-11-quality-gates.md): hooks and CI call the same scripts, so the gate fires locally before a push — not only after it). They run after `pnpm run typecheck`, which validates the package/vendor build graph that doc-typecheck references. API-extractor golden reports ([the deferred API-extractor-reports proposal](../../proposed/process/2026-06-11-api-extractor-reports.md)) were deliberately **deferred** — low value for an internal monorepo where reviewers already see the source diff, and a heavy, finicky dependency. diff --git a/docs/rfc/implemented/process/2026-06-20-generated-cordis-catalog.md b/docs/rfc/implemented/process/2026-06-20-generated-cordis-catalog.md index ae07f4b14c..a53b1f40f6 100644 --- a/docs/rfc/implemented/process/2026-06-20-generated-cordis-catalog.md +++ b/docs/rfc/implemented/process/2026-06-20-generated-cordis-catalog.md @@ -14,7 +14,7 @@ This is the wiring-axis complement to the [core-data-structures catalog](../../. Generate the catalog from source instead of hand-maintaining a table and verifying a subset. -`scripts/gen-cordis-catalog.ts` walks the `interface Events` and `interface Context` declarations (plus the service classes) with the TypeScript compiler API and emits `docs/cordis-catalog/events-and-services.md` — one `## Events` section (grouped by scope, each event rendered as signature + mode badge + its source JSDoc) and one `## Services` section (each `ctx.` with its public method signatures + class JSDoc). It mirrors the `gen-module-graph` pattern exactly: `--write` regenerates, `--check` fails if the committed file is stale, output is deterministic (sorted), and the file is a build artifact that is never hand-edited. `verify-cordis-catalog` (the `--check`) runs inside `doc-sync`, so the freshness gate fires in the same lefthook pre-push and CI paths as every other doc gate. +`scripts/gen-cordis-catalog.ts` walks the `interface Events` and `interface Context` declarations (plus the service classes) with the TypeScript compiler API and emits two sibling pages: `docs/cordis-catalog/events.md` (events grouped by scope, each rendered as signature + mode badge + its source JSDoc, plus the dispatch-mode legend) and `docs/cordis-catalog/services.md` (each `ctx.` with its public method signatures + class JSDoc). The two axes are separate documents — a reader is either finding what to listen to or what to call, and each page scans and deep-links as its own reference instead of one long combined scroll. It mirrors the `gen-module-graph` pattern exactly: `--write` regenerates both, `--check` fails if either committed file is stale, output is deterministic (sorted), and the files are build artifacts that are never hand-edited. `verify-cordis-catalog` (the `--check`) runs inside `doc-sync`, so the freshness gate fires in the same lefthook pre-push and CI paths as every other doc gate. Pure generation is correct here because the codebase is disciplined enough that the AST is the whole truth: every event/service name is a string literal that round-trips to a static declaration — there are no dynamically-named events and no runtime-only services. So a generated doc cannot be wrong, and it closes the undocumented-event gap structurally (generation enumerates source rather than checking a hand-written subset). diff --git a/docs/rfc/implemented/process/2026-07-02-tool-schema-catalog.md b/docs/rfc/implemented/process/2026-07-02-tool-schema-catalog.md index a085568429..479d93c2df 100644 --- a/docs/rfc/implemented/process/2026-07-02-tool-schema-catalog.md +++ b/docs/rfc/implemented/process/2026-07-02-tool-schema-catalog.md @@ -4,7 +4,7 @@ Status: implemented (accepted 2026-07-02) ## Context -A reader — a plugin author, a prompt engineer, someone auditing what the agent can do — has no single place that lists the model-facing tools the harness ships. The `name` / `description` / JSON-Schema `parameters` a tool contributes are what the model actually receives (via `ctx.systemPrompt.tools()` off `ctx.tools.schemas()`), but they are scattered across each `defineTool` call in each `packages/*/tool-*` package, buried in string concatenation and runtime spreads. The [cordis events & services catalog](../../../cordis-catalog/events-and-services.md) ([its RFC](2026-06-20-generated-cordis-catalog.md)) documents the *wiring* a plugin works against and the [core-data-structures catalog](../../../core-data-structures/core.md) documents the *vocabulary* those signatures move — but neither documents the *tools* the agent is offered. This RFC adds that third reference surface, `docs/tool-catalog/tools.md`, and a freshness gate so it cannot drift. +A reader — a plugin author, a prompt engineer, someone auditing what the agent can do — has no single place that lists the model-facing tools the harness ships. The `name` / `description` / JSON-Schema `parameters` a tool contributes are what the model actually receives (via `ctx.systemPrompt.tools()` off `ctx.tools.schemas()`), but they are scattered across each `defineTool` call in each `packages/*/tool-*` package, buried in string concatenation and runtime spreads. The cordis [events](../../../cordis-catalog/events.md) & [services](../../../cordis-catalog/services.md) catalogs ([their RFC](2026-06-20-generated-cordis-catalog.md)) document the *wiring* a plugin works against and the [core-data-structures catalog](../../../core-data-structures/core.md) documents the *vocabulary* those signatures move — but neither documents the *tools* the agent is offered. This RFC adds that third reference surface, `docs/tool-catalog/tools.md`, and a freshness gate so it cannot drift. ## Decision diff --git a/docs/tool-catalog/tools.md b/docs/tool-catalog/tools.md index 5b81b9d9d7..b1218c3871 100644 --- a/docs/tool-catalog/tools.md +++ b/docs/tool-catalog/tools.md @@ -3,7 +3,7 @@ # Tool Schema Catalog -Every model-facing tool a shipped plugin contributes to `ctx.tools`: the `name`, `description`, and JSON-Schema `parameters` the model receives via the system-prompt assembly. It complements the [cordis events & services catalog](../cordis-catalog/events-and-services.md) (the wiring a plugin listens to and calls) and [core-data-structures/](../core-data-structures/core.md) (the types those signatures move) — this page is the *tools* the agent is offered. +Every model-facing tool a shipped plugin contributes to `ctx.tools`: the `name`, `description`, and JSON-Schema `parameters` the model receives via the system-prompt assembly. It complements the cordis [events](../cordis-catalog/events.md) & [services](../cordis-catalog/services.md) catalogs (the wiring a plugin listens to and calls) and [core-data-structures/](../core-data-structures/core.md) (the types those signatures move) — this page is the *tools* the agent is offered. This file is GENERATED and verified fresh by `pnpm run verify-tool-catalog` (part of `doc-sync`) — do not edit it by hand. Unlike the cordis catalog (a pure source-AST pass), this generator BOOTS each tool plugin on a real context and reads `ctx.tools.schemas()`, because a tool schema is not statically knowable (runtime-spread enums, concatenated descriptions, config-driven names, raw-JSON-Schema MCP tools). A completeness guard globs `packages/*/tool-*` and fails if any package is missing from the generator's boot manifest, so a new tool cannot be silently undocumented. See [the tool-schema-catalog RFC](../rfc/implemented/process/2026-07-02-tool-schema-catalog.md). diff --git a/packages/fs/fs/README.md b/packages/fs/fs/README.md index 7e7c7250de..ac2866802b 100644 --- a/packages/fs/fs/README.md +++ b/packages/fs/fs/README.md @@ -31,7 +31,7 @@ The mutation runs inside the backend's per-target lock either way, so an uncondi ## The `fs/*` policy events -This package declares three events (see the generated [catalog](../../../docs/cordis-catalog/events-and-services.md)) so the emitter (`@deepseek-ai/dsh-tool-fs`) and the policy listener (`@deepseek-ai/dsh-fs-policy`) share a vocabulary without the emitter depending on the policy plugin. `fs/write-intent` and `fs/edit-intent` are single-slot decision waterfalls (the listener fully decides, never calling `next()`); `fs/observed` is a fire-and-forget recording event. They carry only `dsh-fs` vocabulary plus an opaque `object` actor — no model-facing concepts and no agent/session owner structure. +This package declares three events (see the generated [events catalog](../../../docs/cordis-catalog/events.md)) so the emitter (`@deepseek-ai/dsh-tool-fs`) and the policy listener (`@deepseek-ai/dsh-fs-policy`) share a vocabulary without the emitter depending on the policy plugin. `fs/write-intent` and `fs/edit-intent` are single-slot decision waterfalls (the listener fully decides, never calling `next()`); `fs/observed` is a fire-and-forget recording event. They carry only `dsh-fs` vocabulary plus an opaque `object` actor — no model-facing concepts and no agent/session owner structure. ## A provider seam, not the policy layer diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts index 62cf2bbdc6..3d215a8b2a 100644 --- a/scripts/gen-cordis-catalog.ts +++ b/scripts/gen-cordis-catalog.ts @@ -1,25 +1,28 @@ /** - * Generate (and verify) the cordis events + services catalog in - * docs/cordis-catalog/events-and-services.md. + * Generate (and verify) the cordis events and services catalogs in + * docs/cordis-catalog/events.md and docs/cordis-catalog/services.md. * - * The catalog is the WIRING-axis reference: every cordis event a plugin can - * listen to (exact signature + dispatch mode) and every `ctx.` service it - * can call (exact public interface). It complements the core-data-structures - * catalog (the VOCABULARY axis — the types these signatures move around). + * The two pages are the WIRING-axis reference, one axis each: every cordis + * event a plugin can listen to (exact signature + dispatch mode) and every + * `ctx.` service it can call (exact public interface). They complement the + * core-data-structures catalog (the VOCABULARY axis — the types these + * signatures move around). * - * The catalog is FULLY GENERATED from source — never hand-edit it. The codebase - * is disciplined enough that a pure-AST pass captures the whole truthful - * surface: every event/service is a string literal that round-trips to a static - * `interface Events` / `interface Context` declaration (no dynamically-named - * events, no runtime-only services). So the committed file is a build artifact - * and a regenerate-and-diff freshness check (`--check`) makes drift structurally - * impossible. Because generation enumerates source rather than checking a - * hand-written subset, a brand-new event cannot be silently undocumented — it - * appears in the next regenerate, and an un-regenerated file fails `--check`. + * The catalogs are FULLY GENERATED from source — never hand-edit them. The + * codebase is disciplined enough that a pure-AST pass captures the whole + * truthful surface: every event/service is a string literal that round-trips + * to a static `interface Events` / `interface Context` declaration (no + * dynamically-named events, no runtime-only services). So the committed files + * are build artifacts and a regenerate-and-diff freshness check (`--check`) + * makes drift structurally impossible. Because generation enumerates source + * rather than checking a hand-written subset, a brand-new event cannot be + * silently undocumented — it appears in the next regenerate, and an + * un-regenerated file fails `--check`. * - * `tsx scripts/gen-cordis-catalog.ts` → write the catalog - * `tsx scripts/gen-cordis-catalog.ts --check` → exit 1 if the committed file - * is stale (CI / pre-push gate) + * `tsx scripts/gen-cordis-catalog.ts` → write both catalogs + * `tsx scripts/gen-cordis-catalog.ts --check` → exit 1 if a committed + * catalog is stale (CI / + * pre-push gate) * * The HARNESS tier (the `@deepseek-ai/dsh-*` events + services) is rendered in * full from source: signature, the `@mode` badge, and the declaration's JSDoc. @@ -52,7 +55,8 @@ import { resolve } from 'node:path' import ts from 'typescript' const root = resolve(import.meta.dirname, '..') -const OUT = 'docs/cordis-catalog/events-and-services.md' +const OUT_EVENTS = 'docs/cordis-catalog/events.md' +const OUT_SERVICES = 'docs/cordis-catalog/services.md' /** The fenced-block info string for generated signature blocks (skipped by * doc-typecheck, since a bare signature fragment is not standalone-compilable). */ @@ -63,7 +67,8 @@ type Mode = 'emit' | 'waterfall' | 'parallel' | 'serial' /** * Cross-link map: a type name that appears in a signature → the - * core-data-structures page that documents it (path relative to OUT's folder). + * core-data-structures page that documents it (path relative to the catalogs' + * folder). * Hand-curated and catalog-owned, NOT derived from type-equiv.manifest.json — * that manifest documents the `…Map` symbols (`ContentBlockMap`) while * signatures reference the derived UNION names (`ContentBlock`), and it lists a @@ -506,7 +511,7 @@ function typeLinks(signature: string): string { /** Render one harness event entry. */ function renderEvent(e: EventEntry): string[] { - const out = [`#### \`${e.name}\` — ${e.mode}`, ''] + const out = [`### \`${e.name}\` — ${e.mode}`, ''] if (e.doc) out.push(e.doc, '') out.push('```' + FENCE, e.signature, '```', '') const links = typeLinks(e.signature) @@ -518,7 +523,7 @@ function renderEvent(e: EventEntry): string[] { /** Render one harness service entry. */ function renderService(s: ServiceEntry): string[] { const kind = s.abstract ? ' (abstract seam)' : '' - const out = [`### \`ctx.${s.key}\` — \`${s.type}\`${kind}`, ''] + const out = [`## \`ctx.${s.key}\` — \`${s.type}\`${kind}`, ''] if (s.doc) out.push(s.doc, '') if (s.methods.length) { out.push('```' + FENCE, ...s.methods, '```', '') @@ -529,51 +534,71 @@ function renderService(s: ServiceEntry): string[] { return out } -/** Render the full catalog (pure, deterministic given sorted inputs). */ -function render(events: EventEntry[], services: ServiceEntry[]): string { +/** The shared generated-file banner comment. */ +const BANNER = [ + '', + '', +] + +/** The shared GENERATED + freshness-gate + fence notice paragraph. */ +const GATE_NOTICE = 'This file is GENERATED from source (`scripts/gen-cordis-catalog.ts`) and verified fresh by `pnpm run verify-cordis-catalog` (part of `doc-sync`) — do not edit it by hand. Signature blocks use a `ts cordis-catalog` fence (skipped by doc-typecheck, since a bare signature is not standalone-compilable). Type names in a signature link to the page that documents them.' + +/** Render the events catalog (pure, deterministic given sorted inputs). */ +function renderEvents(events: EventEntry[]): string { const lines: string[] = [ - '', + ...BANNER, + '# Cordis Events Catalog', '', - '# Cordis Events & Services Catalog', + 'Every cordis event a plugin can listen to: exact signature, dispatch mode, and the declaration\'s JSDoc. This is one axis of the **wiring** reference a plugin author works against — the callable `ctx.` surface is the sibling [services catalog](services.md), and [core-data-structures/](../core-data-structures/core.md) catalogs the *data structures* these signatures move around.', '', - 'An index reference to the **wiring** a plugin author works against: every cordis event you can listen to (exact signature + dispatch mode) and every `ctx.` service you can call (exact public interface). It complements [core-data-structures/](../core-data-structures/core.md), which catalogs the *data structures* these signatures move around — this page is the verbs, that page is the nouns.', + GATE_NOTICE, '', - 'This file is GENERATED from source (`scripts/gen-cordis-catalog.ts`) and verified fresh by `pnpm run verify-cordis-catalog` (part of `doc-sync`) — do not edit it by hand. Signature blocks use a `ts cordis-catalog` fence (skipped by doc-typecheck, since a bare signature is not standalone-compilable). Type names in a signature link to the page that documents them.', - '', - 'The **harness tier** below (the `@deepseek-ai/dsh-*` packages) is the vocabulary this repo owns. The **inherited tier** at the end is the cordis-core + loader/hmr/timer surface a plugin also sees — pinned vendor source, summarized tersely.', - '', - '## Events', + 'The **harness tier** below (the `@deepseek-ai/dsh-*` packages) is the vocabulary this repo owns, grouped by scope. The **inherited tier** at the end is the cordis-core + loader/hmr/timer event surface a plugin also sees — pinned vendor source, summarized tersely.', '', 'Dispatch modes: **emit** (fire-and-forget), **waterfall** (each listener gets `next()` and may transform or veto — see [waterfall semantics](../architecture.md#cordis-waterfall-semantics-important)), **parallel** (awaited fan-out; all listeners run), **serial** (awaited in registration order until one returns a bail value — anything other than `null`, `false`, or `undefined`).', '', ] const scopes = [...new Set(events.map(e => e.scope))].sort() for (const scope of scopes) { - lines.push(`### \`${scope}/*\``, '') + lines.push(`## \`${scope}/*\``, '') for (const e of events.filter(x => x.scope === scope).sort((a, b) => a.name.localeCompare(b.name))) { lines.push(...renderEvent(e)) } } lines.push( - '## Services', + '## Inherited events (cordis core + loader/hmr/timer)', '', - 'The `ctx.` services the harness provides. An abstract seam (e.g. `ctx.bash`) is implemented by a separate package; the interface is what consumers code against.', - '', - ) - for (const s of services) lines.push(...renderService(s)) - lines.push( - '## Inherited tier (cordis core + loader/hmr/timer)', - '', - 'The framework surface every plugin inherits, beyond the harness vocabulary above. This is pinned vendor source ([vendoring policy](../../vendor/README.md)); it is summarized here so the catalog is a complete picture of what `ctx` and the event bus offer, without elevating framework internals to the harness tier\'s prominence.', - '', - '### Inherited events', + 'The framework events every plugin also sees, beyond the harness vocabulary above. This is pinned vendor source ([vendoring policy](../../vendor/README.md)); it is summarized here so the page is a complete picture of the event bus, without elevating framework internals to the harness tier\'s prominence.', '', ) for (const e of INHERITED_EVENTS) { lines.push(`- \`${e.name}\` — ${e.summary} ([\`${e.source}\`](../../${e.source.split(':')[0]}))`) } - lines.push('', '### Inherited `ctx` members', '') + lines.push('') + return lines.join('\n') +} + +/** Render the services catalog (pure, deterministic given sorted inputs). */ +function renderServices(services: ServiceEntry[]): string { + const lines: string[] = [ + ...BANNER, + '# Cordis Services Catalog', + '', + 'Every `ctx.` service a plugin can call: the exact public interface plus the class JSDoc. This is one axis of the **wiring** reference a plugin author works against — the events a plugin listens to are the sibling [events catalog](events.md), and [core-data-structures/](../core-data-structures/core.md) catalogs the *data structures* these signatures move around. An abstract seam (e.g. `ctx.bash`) is implemented by a separate package; the interface is what consumers code against.', + '', + GATE_NOTICE, + '', + 'The **harness tier** below (the `@deepseek-ai/dsh-*` packages) is the vocabulary this repo owns. The **inherited tier** at the end is the cordis-core + loader/hmr/timer `ctx` surface a plugin also sees — pinned vendor source, summarized tersely.', + '', + ] + for (const s of services) lines.push(...renderService(s)) + lines.push( + '## Inherited `ctx` members (cordis core + loader/hmr/timer)', + '', + 'The framework `ctx` surface every plugin also sees, beyond the harness services above. This is pinned vendor source ([vendoring policy](../../vendor/README.md)); it is summarized here so the page is a complete picture of what `ctx` offers, without elevating framework internals to the harness tier\'s prominence.', + '', + ) for (const s of INHERITED_SERVICES) { lines.push(`- \`${s.name}\` — ${s.summary} ([\`${s.source}\`](../../${s.source.split(':')[0]}))`) } @@ -581,31 +606,38 @@ function render(events: EventEntry[], services: ServiceEntry[]): string { return lines.join('\n') } -/** CLI entry: `--write` (default) writes the catalog, `--check` fails if stale. - * Guarded behind an entry-point check so importing this module for tests neither - * regenerates the committed file nor calls process.exit. */ +/** CLI entry: `--write` (default) writes both catalogs, `--check` fails if + * either is stale. Guarded behind an entry-point check so importing this module + * for tests neither regenerates the committed files nor calls process.exit. */ function main(): void { - const content = render(collectEvents(), collectServices()) + const outputs: [string, string][] = [ + [OUT_EVENTS, renderEvents(collectEvents())], + [OUT_SERVICES, renderServices(collectServices())], + ] if (process.argv.includes('--check')) { - let committed: string | null = null - try { - committed = readFileSync(resolve(root, OUT), 'utf8') - } catch { - // Only ENOENT (not yet generated) is expected; a present-but-unreadable - // file is not a state this repo produces. Either way the remedy is the - // same — regenerate — so treat a read failure as "stale". - committed = null + const stale: string[] = [] + for (const [out, content] of outputs) { + let committed: string | null = null + try { + committed = readFileSync(resolve(root, out), 'utf8') + } catch { + // Only ENOENT (not yet generated) is expected; a present-but-unreadable + // file is not a state this repo produces. Either way the remedy is the + // same — regenerate — so treat a read failure as "stale". + committed = null + } + if (committed !== content) stale.push(out) } - if (committed === content) { - console.log(`gen-cordis-catalog: ${OUT} is up to date.`) + if (stale.length === 0) { + console.log(`gen-cordis-catalog: ${OUT_EVENTS} and ${OUT_SERVICES} are up to date.`) process.exit(0) } - console.error(`gen-cordis-catalog: ${OUT} is stale. Run \`pnpm run gen-cordis-catalog\` and commit ${OUT}.`) + console.error(`gen-cordis-catalog: ${stale.join(' and ')} ${stale.length === 1 ? 'is' : 'are'} stale. Run \`pnpm run gen-cordis-catalog\` and commit the result.`) process.exit(1) } - writeFileSync(resolve(root, OUT), content) - console.log(`gen-cordis-catalog: wrote ${OUT}.`) + for (const [out, content] of outputs) writeFileSync(resolve(root, out), content) + console.log(`gen-cordis-catalog: wrote ${OUT_EVENTS} and ${OUT_SERVICES}.`) } // Run only when invoked as a script, not when imported by a test. diff --git a/scripts/gen-persistence-catalog.ts b/scripts/gen-persistence-catalog.ts index 6d1988beb7..61e70d4f97 100644 --- a/scripts/gen-persistence-catalog.ts +++ b/scripts/gen-persistence-catalog.ts @@ -404,7 +404,7 @@ export function render(events: AnnotatedLogEventEntry[]): string { '', '# Persistence Log Event Catalog', '', - 'Every event type that can appear in a session\'s durable event log: each member of the merge-extensible `SessionEventMap` — the owning vocabulary in `@deepseek-ai/dsh-session` plus every plugin declaration merge in this repo — with the payload it carries, its surface badge, and the declaration it comes from. It complements [session.md](../core-data-structures/session.md) (the `SessionEvent` envelope, surface list, and `deriveMessages()` projection), [persistence.md](../core-data-structures/persistence.md) (how the log is made durable), and the [cordis catalog](../cordis-catalog/events-and-services.md) (the live bus wiring — a log event is NOT a cordis event; it reaches listeners via the single `session/event` emit).', + 'Every event type that can appear in a session\'s durable event log: each member of the merge-extensible `SessionEventMap` — the owning vocabulary in `@deepseek-ai/dsh-session` plus every plugin declaration merge in this repo — with the payload it carries, its surface badge, and the declaration it comes from. It complements [session.md](../core-data-structures/session.md) (the `SessionEvent` envelope, surface list, and `deriveMessages()` projection), [persistence.md](../core-data-structures/persistence.md) (how the log is made durable), and the [cordis events catalog](../cordis-catalog/events.md) (the live bus wiring — a log event is NOT a cordis event; it reaches listeners via the single `session/event` emit).', '', 'This file is GENERATED from source (`scripts/gen-persistence-catalog.ts`) and verified fresh by `pnpm run verify-persistence-catalog` (part of `doc-sync`) — do not edit it by hand. Payload blocks use a `ts persistence-catalog` fence (skipped by doc-typecheck, since a bare payload fragment is not standalone-compilable). Type names in a payload link to the page that documents them. See [the persistence-log-catalog RFC](../rfc/implemented/process/2026-07-04-persistence-log-catalog.md).', '', diff --git a/scripts/gen-tool-catalog.ts b/scripts/gen-tool-catalog.ts index 0ffc3565f4..9dd8e0a6e6 100644 --- a/scripts/gen-tool-catalog.ts +++ b/scripts/gen-tool-catalog.ts @@ -233,7 +233,7 @@ export function render(catalog: ToolCatalog): string { '', '# Tool Schema Catalog', '', - 'Every model-facing tool a shipped plugin contributes to `ctx.tools`: the `name`, `description`, and JSON-Schema `parameters` the model receives via the system-prompt assembly. It complements the [cordis events & services catalog](../cordis-catalog/events-and-services.md) (the wiring a plugin listens to and calls) and [core-data-structures/](../core-data-structures/core.md) (the types those signatures move) — this page is the *tools* the agent is offered.', + 'Every model-facing tool a shipped plugin contributes to `ctx.tools`: the `name`, `description`, and JSON-Schema `parameters` the model receives via the system-prompt assembly. It complements the cordis [events](../cordis-catalog/events.md) & [services](../cordis-catalog/services.md) catalogs (the wiring a plugin listens to and calls) and [core-data-structures/](../core-data-structures/core.md) (the types those signatures move) — this page is the *tools* the agent is offered.', '', 'This file is GENERATED and verified fresh by `pnpm run verify-tool-catalog` (part of `doc-sync`) — do not edit it by hand. Unlike the cordis catalog (a pure source-AST pass), this generator BOOTS each tool plugin on a real context and reads `ctx.tools.schemas()`, because a tool schema is not statically knowable (runtime-spread enums, concatenated descriptions, config-driven names, raw-JSON-Schema MCP tools). A completeness guard globs `packages/*/tool-*` and fails if any package is missing from the generator\'s boot manifest, so a new tool cannot be silently undocumented. See [the tool-schema-catalog RFC](../rfc/implemented/process/2026-07-02-tool-schema-catalog.md).', '', From 8dd8ce21db13121195c3d7165a18a807d99aa1df Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 5 Jul 2026 01:22:17 +0800 Subject: [PATCH 05/14] docs: make the service map the front door of architecture.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Layering ASCII diagram was cluttered, lumped THE concrete loop plugin undifferentiated into a grab-bag plugins box, omitted dsh-agent-core and the app packages, and restated the dependency rule that packages/README.md already owns — while being the first thing a reader linked from README hits. - Drop the Layering section; the tier story becomes one intro sentence and the dependency rule a one-line Service map footer linking packages/README.md#dependencies. - Split the Service map into the spine (packages/core/) vs the swappable capability seams; annotate ctx.agentLoop as THE concrete loop plugin. - Promote Cordis waterfall semantics to a top-level section placed before first waterfall use; drop '(important)' from the heading (anchor swept: AGENTS.md, gen-cordis-catalog.ts + regenerated catalog). - Promote Event taxonomy to a top-level section (anchor unchanged). - Retitle 'The vocabulary (dsh-llm)' to 'Content blocks and streaming (dsh-llm)' so the heading names its content (citation swept: llm-streaming.md). Net -29 words (1851 -> 1822); the 1890 ceiling stands, keeping the manifest's working margin. --- AGENTS.md | 2 +- docs/architecture.md | 65 +++++++++------------- docs/cordis-catalog/events-and-services.md | 2 +- docs/core-data-structures/llm-streaming.md | 2 +- scripts/gen-cordis-catalog.ts | 2 +- 5 files changed, 31 insertions(+), 42 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index c8a1cd226d..d120af7524 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -85,7 +85,7 @@ Real-API tests and demos read `DEEPSEEK_API_KEY` (and optional `DEEPSEEK_BASE_UR - **Registrations are effects**: every contribution goes through `ctx.effect()` / `ctx.on()`; a registry's `register()` returns the disposer. - **Typed events via declaration merging**; extensible unions use the merge-extensible-map pattern (`ContentBlockMap`, `SessionEventMap`, …). Every new event's JSDoc carries an `@mode` tag and a `@param` per payload parameter (`this`/trailing `next` exempt); every public service-class method documents each parameter and non-void return (`@param`/`@returns`) — the catalog generator hard-errors otherwise ([completeness RFC](docs/rfc/implemented/process/2026-07-04-cordis-jsdoc-completeness-gate.md)); mode semantics are in the [generated catalog](docs/cordis-catalog/events-and-services.md) header and [the catalog RFC](docs/rfc/implemented/process/2026-06-20-generated-cordis-catalog.md). - **Discriminated unions: `switch` on the tag**, not if-chains. Closed unions end with `default: assertNever(...)`; merge-extensible unions must NOT — handle known cases and fall through `default` with a comment. -- **Waterfall listeners MUST call `next()`** to delegate; returning without it is the veto ([semantics](docs/architecture.md#cordis-waterfall-semantics-important)). +- **Waterfall listeners MUST call `next()`** to delegate; returning without it is the veto ([semantics](docs/architecture.md#cordis-waterfall-semantics)). - **Plugins, not loop changes**: new behavior goes on the documented extension seams; changing `agent-loop` requires updating docs/architecture.md. - **Capability seams are three packages** — interface / implementation / consumer ([capability seams](docs/rfc/implemented/architecture/2026-06-13-capability-seams.md)); don't split preemptively. - **Explicit > implicit at package seams**: no optional field silently filled by a hidden `?? default` inside `run()`; defaulting is an explicit `resolve(request): Spec` step in the owning implementation (the `dsh-bash` request/spec split is the template). diff --git a/docs/architecture.md b/docs/architecture.md index 44302fb726..eb18234d3f 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,51 +1,50 @@ # DeepSeek Harness Architecture -This document describes the architecture of the DeepSeek Harness — the foundation of **DeepSeek Code**. The governing principle, from the [microkernel design discussion][microkernel-doc]: **everything is a plugin**. The core is deliberately tiny — a handful of abstract services plus one concrete loop plugin (`dsh-agent-loop`) — and every product feature is a plugin against the extension surface described here, without modifying the loop. +This document describes the architecture of the DeepSeek Harness — the foundation of **DeepSeek Code**. The governing principle, from the [microkernel design discussion][microkernel-doc]: **everything is a plugin**. The core is deliberately tiny — a handful of abstract services plus one concrete loop plugin (`dsh-agent-loop`) — and every product feature is a plugin against the extension surface described here, without modifying the loop. The stack is three tiers: plugins (the loop itself, seam implementations, model-facing tools, bridges) over interface/service packages (each owning one `ctx` key and its vocabulary) over the vendored Cordis kernel (`vendor/`). This document covers **behavior**; type shapes live in [core-data-structures/](core-data-structures/core.md), the per-event/service reference in the [generated catalog](cordis-catalog/events-and-services.md), per-package contracts in the package READMEs ([map](../packages/README.md)). Requirement context: [Coding Harness MVP 需求分析][mvp-doc]. [microkernel-doc]: https://trtgsjkv6r.feishu.cn/wiki/VS9Lw1kQki6mDJk2UHocyuphnsc [mvp-doc]: https://trtgsjkv6r.feishu.cn/wiki/ZwK6wfBE9i91V6kzMGYcgRGanxg -## Layering - -``` -┌────────────────────────────────────────────────────────────────┐ -│ extension + implementation plugins │ -│ dsh-agent-loop — THE concrete loop plugin │ -│ LLM adapters · executors/backends · model-facing tools │ -│ subagent providers · hook bridges · UI bridges │ -├────────────────────────────────────────────────────────────────┤ -│ interface/service packages (each owns a ctx key + vocabulary) │ -│ dsh-agent · dsh-tools · dsh-system-prompt · dsh-session │ -│ dsh-llm · dsh-bash · dsh-fs · dsh-web · dsh-compact │ -│ dsh-subagent · dsh-session-persistence │ -├────────────────────────────────────────────────────────────────┤ -│ vendor/: pinned Cordis framework source (cordis, loader, …) │ -└────────────────────────────────────────────────────────────────┘ -``` - -Dependency rule: extension plugins depend on interfaces, never on `dsh-agent-loop` (the loop is swappable); the sanctioned exception is the composition bundle `dsh-agent-core`, whose job is assembling the concrete spine ([full rule + generated graph](../packages/README.md#dependencies)). - ## Service map +The spine — the product API under `packages/core/`: + +| ctx key | Package | Role | +|---|---|---| +| `ctx.sessions` | dsh-session | creates/holds event-sourced `Session`s | +| `ctx.systemPrompt` | dsh-system-prompt | ordered sections + tool schemas → `assemble()` | +| `ctx.tools` | dsh-tools | tool definitions; `execute()` through waterfall | +| `ctx.agents` | dsh-agent | live `Agent` handles + create/resume factory (returns `AgentHandle { agent, dispose() }`) | +| `ctx.agentLoop` | dsh-agent-loop | THE concrete loop plugin: creates and drives `ReactLoopAgent`s | + +The swappable capability seams: + | ctx key | Package | Role | |---|---|---| | `ctx.llm` | dsh-llm | adapter registry; `stream()` | -| `ctx.sessions` | dsh-session | creates/holds event-sourced `Session`s | | `ctx.sessionPersistence` | dsh-session-persistence | durable persistence: create/append/load/list | -| `ctx.systemPrompt` | dsh-system-prompt | ordered sections + tool schemas → `assemble()` | -| `ctx.tools` | dsh-tools | tool definitions; `execute()` through waterfall | -| `ctx.agents` | dsh-agent | live `Agent` handles + create/resume factory (returns `AgentHandle { agent, dispose() }`) | -| `ctx.agentLoop` | dsh-agent-loop | creates and drives `ReactLoopAgent`s | | `ctx.bash` | dsh-bash | bash execution: foreground runs + background tasks | | `ctx.fs` | dsh-fs | filesystem provider: read/stream, atomic writes/edits; owns the `fs/*` policy events | | `ctx.compact` | dsh-compact | compaction: detect pressure, summarize an older range | | `ctx.web` | dsh-web | search/fetch provider registries + `WebError` taxonomy | | `ctx.subagents` | dsh-subagent | named provider registry for delegating to child agents | +Dependency rule: plugins depend on these interfaces, never on `dsh-agent-loop` — the loop is swappable; the sanctioned exception is the composition bundle `dsh-agent-core`, whose job is assembling the concrete spine ([full rule + generated graph](../packages/README.md#dependencies)). + All registrations go through `ctx.effect()` and return disposers, so hot-reload and fiber disposal clean up automatically (full service interfaces: the [generated catalog](cordis-catalog/events-and-services.md) `## Services` section). +## Cordis waterfall semantics + +`ctx.waterfall` is **around-middleware**, not a value reducer. Each listener receives `(...args, next)`: + +- call `next()` to delegate to later listeners (and ultimately the core behavior), possibly wrapping it; +- return a value **without** calling `next()` to short-circuit (veto); +- listeners run in registration order; `prepend: true` jumps the queue. + +Composition caveat: values propagate through `next()`'s **return value** — a listener that returns a *new* object makes earlier listeners' mutations invisible downstream. Prefer mutate-then-`next()` for cooperative middleware; return a replacement only to take over the result. + ## Capability seams: interface / implementation / consumer Swappable capabilities split into three packages — **interface** (abstract service + vocabulary, owns the ctx key), **implementation** (a concrete subclass loaded as a plugin), **consumer** (what the model and plugins program against) — so each evolves independently; the bash trio is the template ([capability seams RFC](rfc/implemented/architecture/2026-06-13-capability-seams.md)). Keep interface + consumer together when they are one concern (the LLM seam: `dsh-llm` carries both, adapters implement); don't split preemptively. @@ -57,7 +56,7 @@ Two seams bend the template deliberately: > The seam pattern is plain Cordis services + `inject` (a consumer's fiber stays pending until the service exists). Despite the name, `@cordisjs/plugin-capability` is unrelated — a permission-security service (a candidate for the deferred permissions work), not a mechanism for swapping implementations. -## The vocabulary (dsh-llm) +## Content blocks and streaming (dsh-llm) Messages are arrays of typed **content blocks** (`text`, `reasoning`, `tool-call`, `tool-result`); the union derives from the merge-extensible `ContentBlockMap`; the same pattern types `MessageSource`, `FinishReason`, `TurnTrigger`, `TurnEndReason`. The core set is limited to blocks every shipping path honors — multimodal content (images, audio, …) has no core block type; a feature that needs one adds it via the map in the same coordinated change that maps it in the adapters, surfaces it in the UI bridges, and prices it in compaction ([the drop-image RFC](rfc/implemented/simplification/2026-07-04-drop-image-content-block.md)). Streaming is a raw chunk protocol (`block-start` … `finish`) with `BlockAssembler` as the single shared chunk→block assembler; the loop logs raw chunks (replay fidelity) while assembling them. `LlmAdapter` is the provider seam: subclass, implement `stream()`, register via `ctx.llm.registerAdapter(models, adapter)`; `dsh-llm-deepseek` and `dsh-llm-pi-ai` implement the one contract as deliberate design twins ([twin RFC](rfc/implemented/architecture/2026-06-13-twin-llm-adapters.md)). The StreamChunk conventions (usage/finish ordering, raw-string tool arguments, the two sanctioned error paths) are pinned in `dsh-llm/src/types.ts` and [llm-streaming.md](core-data-structures/llm-streaming.md). @@ -138,20 +137,10 @@ A turn ends with one `TurnEndReason` — `completed`, `aborted`, `error`, `dispo **Turn-enclosure invariant**: every session event lives inside a turn, making the turn the single durability/replay boundary — anything after the last `turn/end` is an interrupted-crash tail. `dsh-invariants` enforces it in dev ([invariant RFC](rfc/implemented/architecture/2026-06-15-turn-enclosure-invariant.md)). -### Event taxonomy +## Event taxonomy The `agent/*` events are declared in `dsh-agent` (so nothing depends on the loop package); each other service declares its own (`tools/*`, `llm/*`, `system-prompt/*`, `session/*`). The full catalog — signatures, dispatch modes, prose — is generated from source and freshness-gated: [cordis-catalog/events-and-services.md](cordis-catalog/events-and-services.md). Domain semantics (session = the fact log, agent = the live surface): [the event-domain RFC](rfc/implemented/architecture/2026-06-30-event-domain-semantics.md). -### Cordis waterfall semantics (important) - -`ctx.waterfall` is **around-middleware**, not a value reducer. Each listener receives `(...args, next)`: - -- call `next()` to delegate to later listeners (and ultimately the core behavior), possibly wrapping it; -- return a value **without** calling `next()` to short-circuit (veto); -- listeners run in registration order; `prepend: true` jumps the queue. - -Composition caveat: values propagate through `next()`'s **return value** — a listener that returns a *new* object makes earlier listeners' mutations invisible downstream. Prefer mutate-then-`next()` for cooperative middleware; return a replacement only to take over the result. - ## Extension guide Plugin skeletons (tool, hook/permission gate, UI, protocol bridge) and the feature→mechanism map — which extension seam implements each product feature — live in [the extension cookbook](cookbook/extension-cookbook.md); step-by-step guides: [adding a package](cookbook/adding-a-package.md), [a tool](cookbook/adding-a-tool.md), [an LLM adapter](cookbook/adding-an-llm-adapter.md), [a vendored package](cookbook/adding-a-vendored-package.md). diff --git a/docs/cordis-catalog/events-and-services.md b/docs/cordis-catalog/events-and-services.md index 922a0ea018..9617c2e5fb 100644 --- a/docs/cordis-catalog/events-and-services.md +++ b/docs/cordis-catalog/events-and-services.md @@ -11,7 +11,7 @@ The **harness tier** below (the `@deepseek-ai/dsh-*` packages) is the vocabulary ## Events -Dispatch modes: **emit** (fire-and-forget), **waterfall** (each listener gets `next()` and may transform or veto — see [waterfall semantics](../architecture.md#cordis-waterfall-semantics-important)), **parallel** (awaited fan-out; all listeners run), **serial** (awaited in registration order until one returns a bail value — anything other than `null`, `false`, or `undefined`). +Dispatch modes: **emit** (fire-and-forget), **waterfall** (each listener gets `next()` and may transform or veto — see [waterfall semantics](../architecture.md#cordis-waterfall-semantics)), **parallel** (awaited fan-out; all listeners run), **serial** (awaited in registration order until one returns a bail value — anything other than `null`, `false`, or `undefined`). ### `agent/*` diff --git a/docs/core-data-structures/llm-streaming.md b/docs/core-data-structures/llm-streaming.md index 54b804e95d..fff329310a 100644 --- a/docs/core-data-structures/llm-streaming.md +++ b/docs/core-data-structures/llm-streaming.md @@ -62,7 +62,7 @@ interface TokenUsage { ## The seam -`LlmAdapter` is the provider seam: subclass, implement `stream()`, register with `ctx.llm.registerAdapter(models, adapter)`. The `block-start` / `block-end` `index` correlation and the assembler together mean an adapter only has to emit well-formed chunks — block reassembly is not each adapter's problem. The consumer surface (`ctx.llm.stream()`) and the `llm/stream` waterfall are described in [architecture.md § The vocabulary](../architecture.md#the-vocabulary-dsh-llm). +`LlmAdapter` is the provider seam: subclass, implement `stream()`, register with `ctx.llm.registerAdapter(models, adapter)`. The `block-start` / `block-end` `index` correlation and the assembler together mean an adapter only has to emit well-formed chunks — block reassembly is not each adapter's problem. The consumer surface (`ctx.llm.stream()`) and the `llm/stream` waterfall are described in [architecture.md § Content blocks and streaming](../architecture.md#content-blocks-and-streaming-dsh-llm). `ContentBlockType` (the key set the `index`-correlated blocks carry) derives from `ContentBlockMap`: diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts index 62cf2bbdc6..593eba9721 100644 --- a/scripts/gen-cordis-catalog.ts +++ b/scripts/gen-cordis-catalog.ts @@ -545,7 +545,7 @@ function render(events: EventEntry[], services: ServiceEntry[]): string { '', '## Events', '', - 'Dispatch modes: **emit** (fire-and-forget), **waterfall** (each listener gets `next()` and may transform or veto — see [waterfall semantics](../architecture.md#cordis-waterfall-semantics-important)), **parallel** (awaited fan-out; all listeners run), **serial** (awaited in registration order until one returns a bail value — anything other than `null`, `false`, or `undefined`).', + 'Dispatch modes: **emit** (fire-and-forget), **waterfall** (each listener gets `next()` and may transform or veto — see [waterfall semantics](../architecture.md#cordis-waterfall-semantics)), **parallel** (awaited fan-out; all listeners run), **serial** (awaited in registration order until one returns a bail value — anything other than `null`, `false`, or `undefined`).', '', ] const scopes = [...new Set(events.map(e => e.scope))].sort() From 1316022cc8eeebd348e0960f0e486fa89a44d17c Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 5 Jul 2026 01:25:58 +0800 Subject: [PATCH 06/14] docs: revise graph docs from review --- README.i18n.yaml | 4 +- README.md | 2 +- README.zh.md | 2 +- docs/acp-agent-composition.md | 67 +++ docs/{graphs => acp}/snapshot-replay.md | 2 +- docs/agent-lifecycle.md | 49 ++ docs/architecture.md | 2 +- docs/{graphs => }/capability-seams.md | 24 +- docs/coding-agent-composition.md | 67 +++ docs/development.i18n.yaml | 4 +- docs/development.md | 4 +- docs/development.zh.md | 4 +- docs/echo-agent-composition.md | 40 ++ docs/event-producer-consumer.md | 38 ++ docs/graph-atlas.md | 25 + docs/graphs/README.md | 26 - docs/graphs/agent-lifecycle.md | 49 -- docs/graphs/app-composition.md | 106 ---- docs/graphs/event-producer-consumer.md | 38 -- docs/graphs/hot-reload-disposal.md | 24 - docs/graphs/package-topology.md | 247 --------- docs/graphs/session-surface.md | 25 - docs/graphs/subagent-lineage.md | 27 - docs/graphs/tool-affordance-map.md | 50 -- docs/module-graph.md | 402 +++++++++------ docs/rfc/README.md | 2 +- .../2026-07-03-documentation-graph-atlas.md | 44 +- docs/tool-catalog/tools.md | 18 + docs/{graphs => }/tool-execution-pipeline.md | 12 +- .../core/tools/tests/gen-tool-catalog.spec.ts | 5 + scripts/gen-doc-graphs.ts | 471 ++++++------------ scripts/gen-module-graph.ts | 86 +++- scripts/gen-tool-catalog.ts | 52 +- scripts/verify-mermaid.ts | 7 +- 34 files changed, 882 insertions(+), 1143 deletions(-) create mode 100644 docs/acp-agent-composition.md rename docs/{graphs => acp}/snapshot-replay.md (95%) create mode 100644 docs/agent-lifecycle.md rename docs/{graphs => }/capability-seams.md (51%) create mode 100644 docs/coding-agent-composition.md create mode 100644 docs/echo-agent-composition.md create mode 100644 docs/event-producer-consumer.md create mode 100644 docs/graph-atlas.md delete mode 100644 docs/graphs/README.md delete mode 100644 docs/graphs/agent-lifecycle.md delete mode 100644 docs/graphs/app-composition.md delete mode 100644 docs/graphs/event-producer-consumer.md delete mode 100644 docs/graphs/hot-reload-disposal.md delete mode 100644 docs/graphs/package-topology.md delete mode 100644 docs/graphs/session-surface.md delete mode 100644 docs/graphs/subagent-lineage.md delete mode 100644 docs/graphs/tool-affordance-map.md rename docs/{graphs => }/tool-execution-pipeline.md (71%) diff --git a/README.i18n.yaml b/README.i18n.yaml index c042e0a535..db27519f15 100644 --- a/README.i18n.yaml +++ b/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -README.md: 11db682a995c0327134493c7178bc09e393b44e9 -README.zh.md: f2cd9da367c6c7ec3ba3fcd71d1b9547987555fa +README.md: 53dd3896eb15800125673e7c44f7de02daca9376 +README.zh.md: 5de4c5b6804648f061647d9e315c08a32b42b39b diff --git a/README.md b/README.md index 11db682a99..53dd3896eb 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,6 @@ pnpm run demo:repl # REPL agent demo (needs DEEPSEEK_API_KEY) pnpm run demo:acp # ACP server agent demo (needs DEEPSEEK_API_KEY) ``` -For humans, start with the [development guide](docs/development.md) for local setup, hooks, environment variables, and quality gates, then read the [architecture design](docs/architecture.md) and [documentation graph atlas](docs/graphs/README.md) before package work. Local context lives in [packages/](packages/) and [vendor/](vendor/). +For humans, start with the [development guide](docs/development.md) for local setup, hooks, environment variables, and quality gates, then read the [architecture design](docs/architecture.md) and [documentation graph index](docs/graph-atlas.md) before package work. Local context lives in [packages/](packages/) and [vendor/](vendor/). For agents, follow [AGENTS.md](AGENTS.md). diff --git a/README.zh.md b/README.zh.md index f2cd9da367..5de4c5b680 100644 --- a/README.zh.md +++ b/README.zh.md @@ -15,6 +15,6 @@ pnpm run demo:repl # REPL agent demo (needs DEEPSEEK_API_KEY) pnpm run demo:acp # ACP server agent demo (needs DEEPSEEK_API_KEY) ``` -面向人类读者:先读[开发指南](docs/development.md)了解本地环境搭建、钩子、环境变量与质量门禁,动手改 package 之前再读[架构设计](docs/architecture.md)和 [documentation graph atlas](docs/graphs/README.md)。局部上下文见 [packages/](packages/) 与 [vendor/](vendor/)。 +面向人类读者:先读[开发指南](docs/development.md)了解本地环境搭建、钩子、环境变量与质量门禁,动手改 package 之前再读[架构设计](docs/architecture.md)和[文档关系图索引](docs/graph-atlas.md)。局部上下文见 [packages/](packages/) 与 [vendor/](vendor/)。 面向 agent:遵循 [AGENTS.md](AGENTS.md)。 diff --git a/docs/acp-agent-composition.md b/docs/acp-agent-composition.md new file mode 100644 index 0000000000..db3a59eaed --- /dev/null +++ b/docs/acp-agent-composition.md @@ -0,0 +1,67 @@ + + +# ACP Agent App Composition + +Maintenance mode: hybrid: the leaf plugin list is parsed from its `cordis.yml`; app package expansion is curated from package source. + +The ACP demo exposes the same agent spine over JSON-RPC stdio, with no stdout logger and no pre-created agent; clients create sessions through the ACP bridge. + +```mermaid +flowchart LR + cfg["examples/acp-agent
cordis.yml"] + plugin_acp_llm_deepseek["llm-deepseek
@deepseek-ai/dsh-llm-deepseek"] + cfg --> plugin_acp_llm_deepseek + plugin_acp_bash["bash
@deepseek-ai/dsh-bash-local"] + cfg --> plugin_acp_bash + plugin_acp_acp_agent["acp-agent
@deepseek-ai/dsh-acp-agent"] + cfg --> plugin_acp_acp_agent + plugin_acp_acp_agent --> bundle_agent_core["@deepseek-ai/dsh-agent-core"] + plugin_acp_acp_agent --> bundle_jsonl["@deepseek-ai/dsh-session-persistence-jsonl"] + plugin_acp_acp_agent --> frontdoor_acp["@deepseek-ai/dsh-acp
JSON-RPC stdio bridge
sessions created by client"] + bundle_agent_core --> spine_llm["ctx.llm"] + bundle_agent_core --> spine_sessions["ctx.sessions"] + bundle_agent_core --> spine_tools["ctx.tools + tool-bash"] + bundle_agent_core --> spine_loop["ctx.agents + ctx.agentLoop"] + plugin_acp_subagent["subagent
@deepseek-ai/dsh-subagent"] + cfg --> plugin_acp_subagent + plugin_acp_subagent_spawn["subagent-spawn
@deepseek-ai/dsh-subagent-spawn"] + cfg --> plugin_acp_subagent_spawn + plugin_acp_subagent_fork["subagent-fork
@deepseek-ai/dsh-subagent-fork"] + cfg --> plugin_acp_subagent_fork + plugin_acp_tool_subagent["tool-subagent
@deepseek-ai/dsh-tool-subagent"] + cfg --> plugin_acp_tool_subagent + plugin_acp_tool_subagent_fork["tool-subagent-fork
@deepseek-ai/dsh-tool-subagent"] + cfg --> plugin_acp_tool_subagent_fork + plugin_acp_tool_todo["tool-todo
@deepseek-ai/dsh-tool-todo"] + cfg --> plugin_acp_tool_todo + plugin_acp_fs_local["fs-local
@deepseek-ai/dsh-fs-local"] + cfg --> plugin_acp_fs_local + plugin_acp_fs_policy["fs-policy
@deepseek-ai/dsh-fs-policy"] + cfg --> plugin_acp_fs_policy + plugin_acp_tool_fs["tool-fs
@deepseek-ai/dsh-tool-fs"] + cfg --> plugin_acp_tool_fs + plugin_acp_hooks_claude["hooks-claude
@deepseek-ai/dsh-hooks-claude"] + cfg --> plugin_acp_hooks_claude + plugin_acp_hooks_codex["hooks-codex
@deepseek-ai/dsh-hooks-codex"] + cfg --> plugin_acp_hooks_codex +``` + +| Plugin id | Package / module | +| --- | --- | +| `llm-deepseek` | `@deepseek-ai/dsh-llm-deepseek` | +| `bash` | `@deepseek-ai/dsh-bash-local` | +| `acp-agent` | `@deepseek-ai/dsh-acp-agent` | +| `subagent` | `@deepseek-ai/dsh-subagent` | +| `subagent-spawn` | `@deepseek-ai/dsh-subagent-spawn` | +| `subagent-fork` | `@deepseek-ai/dsh-subagent-fork` | +| `tool-subagent` | `@deepseek-ai/dsh-tool-subagent` | +| `tool-subagent-fork` | `@deepseek-ai/dsh-tool-subagent` | +| `tool-todo` | `@deepseek-ai/dsh-tool-todo` | +| `fs-local` | `@deepseek-ai/dsh-fs-local` | +| `fs-policy` | `@deepseek-ai/dsh-fs-policy` | +| `tool-fs` | `@deepseek-ai/dsh-tool-fs` | +| `hooks-claude` | `@deepseek-ai/dsh-hooks-claude` | +| `hooks-codex` | `@deepseek-ai/dsh-hooks-codex` | + +Source config: [`examples/acp-agent/cordis.yml`](../examples/acp-agent/cordis.yml). diff --git a/docs/graphs/snapshot-replay.md b/docs/acp/snapshot-replay.md similarity index 95% rename from docs/graphs/snapshot-replay.md rename to docs/acp/snapshot-replay.md index 1c5e20520a..0acf3c0a96 100644 --- a/docs/graphs/snapshot-replay.md +++ b/docs/acp/snapshot-replay.md @@ -18,7 +18,7 @@ sequenceDiagram Recorder->>Fixture: session.jsonl + workspace inputs Fixture->>Workspace: seed files and hook configs Fixture->>Replay: recorded StreamChunk script - Replay->>ACP: deterministic llm/stream chunks + Replay->>ACP: deterministic llm/stream chunks ACP->>Workspace: bash, fs, and hook side effects ACP->>Golden: normalized sessionUpdate stream Golden-->>ACP: diff must be empty diff --git a/docs/agent-lifecycle.md b/docs/agent-lifecycle.md new file mode 100644 index 0000000000..eb2744f18a --- /dev/null +++ b/docs/agent-lifecycle.md @@ -0,0 +1,49 @@ + + +# Agent Turn And Step Lifecycle + +Maintenance mode: curated Mermaid sequence; exact event signatures live in the generated Cordis catalog. + +This sequence is the visual companion to [architecture.md](architecture.md#loop-lifecycle-session--turn--step). It keeps durable replay facts on `session/event` and live control/status on `agent/*`. + +```mermaid +sequenceDiagram + participant User + participant Agent + participant Driver + participant Hooks as hook listeners + participant Prompt as ctx.systemPrompt + participant LLM as ctx.llm + participant Tools as ctx.tools + participant Session + participant Persistence + participant SDK as UI or SDK listener + User->>Agent: send(content) + Agent-->>SDK: agent/queued + Agent->>Driver: queued work wakes driver + Driver-->>SDK: agent/status running + Driver->>Session: turn/start + Driver->>Hooks: agent/prompt-submit waterfall + Hooks-->>Driver: allow, block, or add context + Driver->>Session: user/message or rejected turn/end + Driver->>Prompt: system-prompt/assemble waterfall + Driver-->>Driver: agent/pre-step serial checkpoint + Driver->>Session: step/start + Driver->>LLM: agent/request waterfall, then llm/stream waterfall + LLM-->>Driver: StreamChunk* + Driver->>Session: assistant/chunk* + Session-->>SDK: session/event assistant/chunk* + Driver->>Hooks: agent/step-result waterfall + Driver->>Session: assistant/message + Driver->>Session: tool/call + Driver->>Tools: execute through pre and post waterfalls + Tools-->>Session: tool-owned events when applicable + Driver->>Session: tool/result and step/end + Driver->>Hooks: agent/turn-continuation waterfall + Driver->>Session: turn/end + Driver->>Persistence: session/flush parallel checkpoint + Driver-->>SDK: agent/status idle +``` + +SDK users that need replayable transcript data should consume `session/event`; `agent/*` is the live coordination surface for queue/status, prompt interception, request shaping, steering, continuation, and errors. diff --git a/docs/architecture.md b/docs/architecture.md index e4b0bdb5f1..800a1a2099 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -2,7 +2,7 @@ This document describes the architecture of the DeepSeek Harness — the foundation of **DeepSeek Code**. The governing principle, from the [microkernel design discussion][microkernel-doc]: **everything is a plugin**. The core is deliberately tiny — a handful of abstract services plus one concrete loop plugin (`dsh-agent-loop`) — and every product feature is a plugin against the extension surface described here, without modifying the loop. -This document covers **behavior**; type shapes live in [core-data-structures/](core-data-structures/core.md), the per-event/service reference in the [generated catalog](cordis-catalog/events-and-services.md), visual relationship maps in the [documentation graph atlas](graphs/README.md), and per-package contracts in the package READMEs ([map](../packages/README.md)). Requirement context: [Coding Harness MVP 需求分析][mvp-doc]. +This document covers **behavior**; type shapes live in [core-data-structures/](core-data-structures/core.md), the per-event/service reference in the [generated catalog](cordis-catalog/events-and-services.md), visual relationship maps in the [documentation graph index](graph-atlas.md), and per-package contracts in the package READMEs ([map](../packages/README.md)). Requirement context: [Coding Harness MVP 需求分析][mvp-doc]. [microkernel-doc]: https://trtgsjkv6r.feishu.cn/wiki/VS9Lw1kQki6mDJk2UHocyuphnsc [mvp-doc]: https://trtgsjkv6r.feishu.cn/wiki/ZwK6wfBE9i91V6kzMGYcgRGanxg diff --git a/docs/graphs/capability-seams.md b/docs/capability-seams.md similarity index 51% rename from docs/graphs/capability-seams.md rename to docs/capability-seams.md index 60bbd10656..7af76d043b 100644 --- a/docs/graphs/capability-seams.md +++ b/docs/capability-seams.md @@ -128,15 +128,15 @@ flowchart LR | ctx key | Role | Owner | Implementations | Direct consumers | Companion plugins | Note | | --- | --- | --- | --- | --- | --- | --- | -| `ctx.llm` | `seam` | [`llm`](../../packages/llm/llm) | [`llm-deepseek`](../../packages/llm/llm-deepseek), [`llm-pi-ai`](../../packages/llm/llm-pi-ai), [`llm-replay`](../../packages/support/llm-replay) | [`agent-loop`](../../packages/core/agent-loop), [`compact-basic`](../../packages/compact/compact-basic) | - | Adapters register provider implementations; the loop and compaction call the provider-neutral stream service. | -| `ctx.sessions` | `core` | [`session`](../../packages/core/session) | - | [`agent-loop`](../../packages/core/agent-loop), [`agent`](../../packages/core/agent), [`session-persistence`](../../packages/session-persistence/session-persistence), [`subagent-inprocess`](../../packages/subagent/subagent-inprocess), [`invariants`](../../packages/support/invariants) | - | Owns append-only Session instances and emits the durable session event feed. | -| `ctx.sessionPersistence` | `seam` | [`session-persistence`](../../packages/session-persistence/session-persistence) | [`session-persistence-jsonl`](../../packages/session-persistence/session-persistence-jsonl), [`session-persistence-sqlite`](../../packages/session-persistence/session-persistence-sqlite) | [`agent-loop`](../../packages/core/agent-loop), [`acp`](../../packages/ui/acp) | - | Backends persist the same SessionEvent vocabulary; apps choose a backend at composition time. | -| `ctx.systemPrompt` | `core` | [`system-prompt`](../../packages/core/system-prompt) | - | [`agent-loop`](../../packages/core/agent-loop), [`tools`](../../packages/core/tools), [`tool-fs`](../../packages/fs/tool-fs), [`tool-web`](../../packages/web/tool-web) | - | Collects prompt sections and model-facing tool schemas for each step. | -| `ctx.tools` | `core` | [`tools`](../../packages/core/tools) | - | [`agent-loop`](../../packages/core/agent-loop), [`tool-bash`](../../packages/bash/tool-bash), [`tool-fs`](../../packages/fs/tool-fs), [`tool-subagent`](../../packages/subagent/tool-subagent), [`tool-todo`](../../packages/todo/tool-todo), [`tool-web`](../../packages/web/tool-web), [`acp`](../../packages/ui/acp) | - | Registers tool definitions, exposes schemas to the prompt, and routes calls through tools/pre-execute and tools/post-execute. | -| `ctx.agents` | `core` | [`agent`](../../packages/core/agent) | - | [`agent-loop`](../../packages/core/agent-loop), [`acp`](../../packages/ui/acp), [`subagent-inprocess`](../../packages/subagent/subagent-inprocess), [`stdio-agent`](../../packages/ui/stdio-agent), [`invariants`](../../packages/support/invariants) | - | Owns live Agent handles and the create/resume factory seam. | -| `ctx.agentLoop` | `bundle` | [`agent-loop`](../../packages/core/agent-loop) | - | [`agent-core`](../../packages/core/agent-core) | - | The one concrete loop plugin; extension packages depend on dsh-agent events and services, not on this package. | -| `ctx.bash` | `seam` | [`bash`](../../packages/bash/bash) | [`bash-local`](../../packages/bash/bash-local) | [`tool-bash`](../../packages/bash/tool-bash), [`hooks-claude`](../../packages/hooks/hooks-claude), [`hooks-codex`](../../packages/hooks/hooks-codex) | - | The model-facing bash tools and hook bridges consume this seam; sandboxed or remote executors can replace bash-local. | -| `ctx.fs` | `seam` | [`fs`](../../packages/fs/fs) | [`fs-local`](../../packages/fs/fs-local) | [`tool-fs`](../../packages/fs/tool-fs) | [`fs-policy`](../../packages/fs/fs-policy) | tool-fs executes read/write/edit through ctx.fs; fs-policy contributes observed-state checks through the fs/* event gate. | -| `ctx.compact` | `seam` | [`compact`](../../packages/compact/compact) | [`compact-basic`](../../packages/compact/compact-basic) | [`compact-basic`](../../packages/compact/compact-basic) | - | The basic backend currently consumes the pre-step event directly; a model-facing compact tool remains deferred. | -| `ctx.subagents` | `seam` | [`subagent`](../../packages/subagent/subagent) | [`subagent-spawn`](../../packages/subagent/subagent-spawn), [`subagent-fork`](../../packages/subagent/subagent-fork), [`subagent-acp`](../../packages/subagent/subagent-acp), [`subagent-mock`](../../packages/support/subagent-mock) | [`tool-subagent`](../../packages/subagent/tool-subagent) | - | Providers implement transports; tool-subagent exposes one configured provider as a model-facing tool name. | -| `ctx.web` | `seam` | [`web`](../../packages/web/web) | [`web-search-exa`](../../packages/web/web-search-exa), [`web-search-perplexity`](../../packages/web/web-search-perplexity), [`web-search-deepseek`](../../packages/web/web-search-deepseek), [`web-fetch-local`](../../packages/web/web-fetch-local) | [`tool-web`](../../packages/web/tool-web) | - | Search and fetch providers register into one ctx.web seam; tool-web owns the stable model-facing names. | +| `ctx.llm` | `seam` | [`llm`](../packages/llm/llm) | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), [`llm-replay`](../packages/support/llm-replay) | [`agent-loop`](../packages/core/agent-loop), [`compact-basic`](../packages/compact/compact-basic) | - | Adapters register provider implementations; the loop and compaction call the provider-neutral stream service. | +| `ctx.sessions` | `core` | [`session`](../packages/core/session) | - | [`agent-loop`](../packages/core/agent-loop), [`agent`](../packages/core/agent), [`session-persistence`](../packages/session-persistence/session-persistence), [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`invariants`](../packages/support/invariants) | - | Owns append-only Session instances and emits the durable session event feed. | +| `ctx.sessionPersistence` | `seam` | [`session-persistence`](../packages/session-persistence/session-persistence) | [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl), [`session-persistence-sqlite`](../packages/session-persistence/session-persistence-sqlite) | [`agent-loop`](../packages/core/agent-loop), [`acp`](../packages/ui/acp) | - | Backends persist the same SessionEvent vocabulary; apps choose a backend at composition time. | +| `ctx.systemPrompt` | `core` | [`system-prompt`](../packages/core/system-prompt) | - | [`agent-loop`](../packages/core/agent-loop), [`tools`](../packages/core/tools), [`tool-fs`](../packages/fs/tool-fs), [`tool-web`](../packages/web/tool-web) | - | Collects prompt sections and model-facing tool schemas for each step. | +| `ctx.tools` | `core` | [`tools`](../packages/core/tools) | - | [`agent-loop`](../packages/core/agent-loop), [`tool-bash`](../packages/bash/tool-bash), [`tool-fs`](../packages/fs/tool-fs), [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-todo`](../packages/todo/tool-todo), [`tool-web`](../packages/web/tool-web), [`acp`](../packages/ui/acp) | - | Registers tool definitions, exposes schemas to the prompt, and routes calls through tools/pre-execute and tools/post-execute. | +| `ctx.agents` | `core` | [`agent`](../packages/core/agent) | - | [`agent-loop`](../packages/core/agent-loop), [`acp`](../packages/ui/acp), [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`stdio-agent`](../packages/ui/stdio-agent), [`invariants`](../packages/support/invariants) | - | Owns live Agent handles and the create/resume factory seam. | +| `ctx.agentLoop` | `bundle` | [`agent-loop`](../packages/core/agent-loop) | - | [`agent-core`](../packages/core/agent-core) | - | The one concrete loop plugin; extension packages depend on dsh-agent events and services, not on this package. | +| `ctx.bash` | `seam` | [`bash`](../packages/bash/bash) | [`bash-local`](../packages/bash/bash-local) | [`tool-bash`](../packages/bash/tool-bash), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) | - | The model-facing bash tools and hook bridges consume this seam; sandboxed or remote executors can replace bash-local. | +| `ctx.fs` | `seam` | [`fs`](../packages/fs/fs) | [`fs-local`](../packages/fs/fs-local) | [`tool-fs`](../packages/fs/tool-fs) | [`fs-policy`](../packages/fs/fs-policy) | tool-fs executes read/write/edit through ctx.fs; fs-policy contributes observed-state checks through the fs/* event gate. | +| `ctx.compact` | `seam` | [`compact`](../packages/compact/compact) | [`compact-basic`](../packages/compact/compact-basic) | [`compact-basic`](../packages/compact/compact-basic) | - | The basic backend currently consumes the pre-step event directly; a model-facing compact tool remains deferred. | +| `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn`](../packages/subagent/subagent-spawn), [`subagent-fork`](../packages/subagent/subagent-fork), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-mock`](../packages/support/subagent-mock) | [`tool-subagent`](../packages/subagent/tool-subagent) | - | Providers implement transports; tool-subagent exposes one configured provider as a model-facing tool name. | +| `ctx.web` | `seam` | [`web`](../packages/web/web) | [`web-search-exa`](../packages/web/web-search-exa), [`web-search-perplexity`](../packages/web/web-search-perplexity), [`web-search-deepseek`](../packages/web/web-search-deepseek), [`web-fetch-local`](../packages/web/web-fetch-local) | [`tool-web`](../packages/web/tool-web) | - | Search and fetch providers register into one ctx.web seam; tool-web owns the stable model-facing names. | diff --git a/docs/coding-agent-composition.md b/docs/coding-agent-composition.md new file mode 100644 index 0000000000..9a339499f8 --- /dev/null +++ b/docs/coding-agent-composition.md @@ -0,0 +1,67 @@ + + +# Coding Agent App Composition + +Maintenance mode: hybrid: the leaf plugin list is parsed from its `cordis.yml`; app package expansion is curated from package source. + +The coding REPL demo adds the real DeepSeek adapter, filesystem tools, todo_write, compaction, and both subagent transports on top of the stdio app package. + +```mermaid +flowchart LR + cfg["examples/coding-agent
cordis.yml"] + plugin_coding_hmr["hmr
@cordisjs/plugin-hmr"] + cfg --> plugin_coding_hmr + plugin_coding_llm_deepseek["llm-deepseek
@deepseek-ai/dsh-llm-deepseek"] + cfg --> plugin_coding_llm_deepseek + plugin_coding_bash["bash
@deepseek-ai/dsh-bash-local"] + cfg --> plugin_coding_bash + plugin_coding_stdio_agent["stdio-agent
@deepseek-ai/dsh-stdio-agent"] + cfg --> plugin_coding_stdio_agent + plugin_coding_stdio_agent --> bundle_agent_core["@deepseek-ai/dsh-agent-core"] + plugin_coding_stdio_agent --> bundle_jsonl["@deepseek-ai/dsh-session-persistence-jsonl"] + plugin_coding_stdio_agent --> frontdoor_stdio["readline UI
console logger
pre-created main agent"] + bundle_agent_core --> spine_llm["ctx.llm"] + bundle_agent_core --> spine_sessions["ctx.sessions"] + bundle_agent_core --> spine_tools["ctx.tools + tool-bash"] + bundle_agent_core --> spine_loop["ctx.agents + ctx.agentLoop"] + plugin_coding_compact_basic["compact-basic
@deepseek-ai/dsh-compact-basic"] + cfg --> plugin_coding_compact_basic + plugin_coding_subagent["subagent
@deepseek-ai/dsh-subagent"] + cfg --> plugin_coding_subagent + plugin_coding_subagent_spawn["subagent-spawn
@deepseek-ai/dsh-subagent-spawn"] + cfg --> plugin_coding_subagent_spawn + plugin_coding_subagent_fork["subagent-fork
@deepseek-ai/dsh-subagent-fork"] + cfg --> plugin_coding_subagent_fork + plugin_coding_tool_subagent["tool-subagent
@deepseek-ai/dsh-tool-subagent"] + cfg --> plugin_coding_tool_subagent + plugin_coding_tool_subagent_fork["tool-subagent-fork
@deepseek-ai/dsh-tool-subagent"] + cfg --> plugin_coding_tool_subagent_fork + plugin_coding_tool_todo["tool-todo
@deepseek-ai/dsh-tool-todo"] + cfg --> plugin_coding_tool_todo + plugin_coding_fs_local["fs-local
@deepseek-ai/dsh-fs-local"] + cfg --> plugin_coding_fs_local + plugin_coding_fs_policy["fs-policy
@deepseek-ai/dsh-fs-policy"] + cfg --> plugin_coding_fs_policy + plugin_coding_tool_fs["tool-fs
@deepseek-ai/dsh-tool-fs"] + cfg --> plugin_coding_tool_fs +``` + +| Plugin id | Package / module | +| --- | --- | +| `hmr` | `@cordisjs/plugin-hmr` | +| `llm-deepseek` | `@deepseek-ai/dsh-llm-deepseek` | +| `bash` | `@deepseek-ai/dsh-bash-local` | +| `stdio-agent` | `@deepseek-ai/dsh-stdio-agent` | +| `compact-basic` | `@deepseek-ai/dsh-compact-basic` | +| `subagent` | `@deepseek-ai/dsh-subagent` | +| `subagent-spawn` | `@deepseek-ai/dsh-subagent-spawn` | +| `subagent-fork` | `@deepseek-ai/dsh-subagent-fork` | +| `tool-subagent` | `@deepseek-ai/dsh-tool-subagent` | +| `tool-subagent-fork` | `@deepseek-ai/dsh-tool-subagent` | +| `tool-todo` | `@deepseek-ai/dsh-tool-todo` | +| `fs-local` | `@deepseek-ai/dsh-fs-local` | +| `fs-policy` | `@deepseek-ai/dsh-fs-policy` | +| `tool-fs` | `@deepseek-ai/dsh-tool-fs` | + +Source config: [`examples/coding-agent/cordis.yml`](../examples/coding-agent/cordis.yml). diff --git a/docs/development.i18n.yaml b/docs/development.i18n.yaml index 64b874cf8f..7d0922febd 100644 --- a/docs/development.i18n.yaml +++ b/docs/development.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -development.md: 18b379170118efed6e4af07b7fdbe606fa3e3d82 -development.zh.md: 7005ad24be386781113984d535723ff40dd74eb2 +development.md: af8d13e08bdb9789016aa1ad8a1f10f9ecfb783e +development.zh.md: 3163c7a21e8cd0e7896983dc721d550de9f1f5a8 diff --git a/docs/development.md b/docs/development.md index 18b3791701..af8d13e08b 100644 --- a/docs/development.md +++ b/docs/development.md @@ -98,8 +98,8 @@ pnpm run lint:fix # eslint . --fix pnpm run doc-typecheck # compile checked TypeScript snippets in Markdown docs pnpm run gen-cordis-catalog # regenerate docs/cordis-catalog/events-and-services.md from source pnpm run verify-cordis-catalog # fail if the cordis events/services catalog is stale -pnpm run gen-doc-graphs # regenerate docs/graphs/*.md from source and curated graph definitions -pnpm run verify-doc-graphs # fail if docs/graphs/*.md is stale +pnpm run gen-doc-graphs # regenerate generated relationship docs from source and curated graph definitions +pnpm run verify-doc-graphs # fail if generated relationship docs are stale pnpm run gen-rfc-index # regenerate the docs/rfc/README.md index tables from the RFC tree pnpm run verify-md-wrap # fail on hard-wrapped prose paragraphs in docs/README markdown pnpm run verify-mermaid # fail if a ```mermaid diagram has invalid Mermaid syntax diff --git a/docs/development.zh.md b/docs/development.zh.md index 7005ad24be..3163c7a21e 100644 --- a/docs/development.zh.md +++ b/docs/development.zh.md @@ -98,8 +98,8 @@ pnpm run lint:fix # eslint . --fix pnpm run doc-typecheck # compile checked TypeScript snippets in Markdown docs pnpm run gen-cordis-catalog # regenerate docs/cordis-catalog/events-and-services.md from source pnpm run verify-cordis-catalog # fail if the cordis events/services catalog is stale -pnpm run gen-doc-graphs # regenerate docs/graphs/*.md from source and curated graph definitions -pnpm run verify-doc-graphs # fail if docs/graphs/*.md is stale +pnpm run gen-doc-graphs # regenerate generated relationship docs from source and curated graph definitions +pnpm run verify-doc-graphs # fail if generated relationship docs are stale pnpm run gen-rfc-index # regenerate the docs/rfc/README.md index tables from the RFC tree pnpm run verify-md-wrap # fail on hard-wrapped prose paragraphs in docs/README markdown pnpm run verify-mermaid # fail if a ```mermaid diagram has invalid Mermaid syntax diff --git a/docs/echo-agent-composition.md b/docs/echo-agent-composition.md new file mode 100644 index 0000000000..1e1e2719cf --- /dev/null +++ b/docs/echo-agent-composition.md @@ -0,0 +1,40 @@ + + +# Echo Agent App Composition + +Maintenance mode: hybrid: the leaf plugin list is parsed from its `cordis.yml`; app package expansion is curated from package source. + +The echo demo swaps in a local mock LLM and teaching echo tool, then loads the stdio app package for the shared spine and terminal front door. + +```mermaid +flowchart LR + cfg["examples/echo-agent
cordis.yml"] + plugin_echo_hmr["hmr
@cordisjs/plugin-hmr"] + cfg --> plugin_echo_hmr + plugin_echo_mock_llm["mock-llm
./src/mock-llm.ts"] + cfg --> plugin_echo_mock_llm + plugin_echo_echo_tool["echo-tool
./src/echo-tool.ts"] + cfg --> plugin_echo_echo_tool + plugin_echo_bash["bash
@deepseek-ai/dsh-bash-local"] + cfg --> plugin_echo_bash + plugin_echo_stdio_agent["stdio-agent
@deepseek-ai/dsh-stdio-agent"] + cfg --> plugin_echo_stdio_agent + plugin_echo_stdio_agent --> bundle_agent_core["@deepseek-ai/dsh-agent-core"] + plugin_echo_stdio_agent --> bundle_jsonl["@deepseek-ai/dsh-session-persistence-jsonl"] + plugin_echo_stdio_agent --> frontdoor_stdio["readline UI
console logger
pre-created main agent"] + bundle_agent_core --> spine_llm["ctx.llm"] + bundle_agent_core --> spine_sessions["ctx.sessions"] + bundle_agent_core --> spine_tools["ctx.tools + tool-bash"] + bundle_agent_core --> spine_loop["ctx.agents + ctx.agentLoop"] +``` + +| Plugin id | Package / module | +| --- | --- | +| `hmr` | `@cordisjs/plugin-hmr` | +| `mock-llm` | `./src/mock-llm.ts` | +| `echo-tool` | `./src/echo-tool.ts` | +| `bash` | `@deepseek-ai/dsh-bash-local` | +| `stdio-agent` | `@deepseek-ai/dsh-stdio-agent` | + +Source config: [`examples/echo-agent/cordis.yml`](../examples/echo-agent/cordis.yml). diff --git a/docs/event-producer-consumer.md b/docs/event-producer-consumer.md new file mode 100644 index 0000000000..63cedd7510 --- /dev/null +++ b/docs/event-producer-consumer.md @@ -0,0 +1,38 @@ + + +# Event Producer And Consumer Matrix + +Maintenance mode: hybrid generated: Cordis event declarations and most producer/listener edges are AST-scanned; dynamic dispatch sites are classified in `scripts/gen-doc-graphs.ts`. + +This matrix shows which packages dispatch each harness-owned event and which packages listen to it. It is intentionally a table rather than one large graph: events are many-to-many, and dense relation data is easier to review in rows. Dynamic dispatch overrides cover sites that deliberately bypass `ctx.emit`, such as subagent lifecycle containment. + +| Event | Mode | Declared in | Dispatchers | Listeners | +| --- | --- | --- | --- | --- | +| `agent/created` | `emit` | [`packages/core/agent/src/types.ts:234`](../packages/core/agent/src/types.ts) | [`agent`](../packages/core/agent) (`emit`) | [`stdio-agent`](../packages/ui/stdio-agent) | +| `agent/disposed` | `emit` | [`packages/core/agent/src/types.ts:241`](../packages/core/agent/src/types.ts) | [`agent`](../packages/core/agent) (`emit`) | [`stdio-agent`](../packages/ui/stdio-agent) | +| `agent/error` | `emit` | [`packages/core/agent/src/types.ts:389`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | - | +| `agent/pre-step` | `serial` | [`packages/core/agent/src/types.ts:319`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`compact-basic`](../packages/compact/compact-basic) | +| `agent/prompt-submit` | `waterfall` | [`packages/core/agent/src/types.ts:332`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) | +| `agent/queued` | `emit` | [`packages/core/agent/src/types.ts:259`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | - | +| `agent/request` | `waterfall` | [`packages/core/agent/src/types.ts:345`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`), [`compact-basic`](../packages/compact/compact-basic) (`waterfall`) | - | +| `agent/session-start` | `emit` | [`packages/core/agent/src/types.ts:274`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) | +| `agent/status` | `emit` | [`packages/core/agent/src/types.ts:250`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/ui/acp), [`invariants`](../packages/support/invariants), [`stdio-agent`](../packages/ui/stdio-agent) | +| `agent/steering` | `emit` | [`packages/core/agent/src/types.ts:379`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | - | +| `agent/step-result` | `waterfall` | [`packages/core/agent/src/types.ts:355`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | - | +| `agent/turn-continuation` | `waterfall` | [`packages/core/agent/src/types.ts:368`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) | +| `fs/edit-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:123`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`) | [`fs-policy`](../packages/fs/fs-policy) | +| `fs/observed` | `emit` | [`packages/fs/fs/src/index.ts:138`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`emit`) | [`fs-policy`](../packages/fs/fs-policy) | +| `fs/write-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:109`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`) | [`fs-policy`](../packages/fs/fs-policy) | +| `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:32`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`waterfall`) | [`llm-replay`](../packages/support/llm-replay) | +| `session/created` | `emit` | [`packages/core/session/src/index.ts:36`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`emit`) | [`invariants`](../packages/support/invariants), [`session-persistence`](../packages/session-persistence/session-persistence) | +| `session/event` | `emit` | [`packages/core/session/src/index.ts:44`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`emit`) | [`acp`](../packages/ui/acp), [`invariants`](../packages/support/invariants), [`session-persistence`](../packages/session-persistence/session-persistence), [`stdio-agent`](../packages/ui/stdio-agent) | +| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:54`](../packages/core/session/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`parallel`) | [`session-persistence`](../packages/session-persistence/session-persistence) | +| `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:77`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../packages/hooks/hooks-claude) | +| `subagent/start` | `emit` | [`packages/subagent/subagent/src/index.ts:70`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../packages/hooks/hooks-claude) | +| `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:26`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | - | +| `system-prompt/change` | `emit` | [`packages/core/system-prompt/src/index.ts:32`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`emit`) | - | +| `tools/change` | `emit` | [`packages/core/tools/src/index.ts:87`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`emit`) | - | +| `tools/post-execute` | `waterfall` | [`packages/core/tools/src/index.ts:82`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) | +| `tools/pre-execute` | `waterfall` | [`packages/core/tools/src/index.ts:66`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) | +| `web/providers-change` | `emit` | [`packages/web/web/src/index.ts:65`](../packages/web/web/src/index.ts) | [`web`](../packages/web/web) (`emit`) | - | diff --git a/docs/graph-atlas.md b/docs/graph-atlas.md new file mode 100644 index 0000000000..09875015f8 --- /dev/null +++ b/docs/graph-atlas.md @@ -0,0 +1,25 @@ + + +# Documentation Graph Index + +Maintenance mode: mixed: each linked page declares generated, hybrid, or curated mode. + +These diagrams are the relationship layer above the generated catalogs. Use them to navigate package topology, capability seams, event flow, model-facing tools, app composition, and runtime lifecycle paths. Exact signatures and type shapes still live in [cordis-catalog/](cordis-catalog/events-and-services.md), [tool-catalog/](tool-catalog/tools.md), and [core-data-structures/](core-data-structures/core.md). + +The process decision behind this index is recorded in [the documentation graph RFC](rfc/implemented/process/2026-07-03-documentation-graph-atlas.md). + +| Graph | Mode | +| --- | --- | +| [module dependency graph](module-graph.md) | `generated` | +| [tool schema catalog and package map](tool-catalog/tools.md) | `generated` | +| [capability seams and core services](capability-seams.md) | `hybrid generated` | +| [echo-agent app composition](echo-agent-composition.md) | `hybrid generated` | +| [coding-agent app composition](coding-agent-composition.md) | `hybrid generated` | +| [acp-agent app composition](acp-agent-composition.md) | `hybrid generated` | +| [event producer/consumer matrix](event-producer-consumer.md) | `hybrid generated` | +| [agent turn and step lifecycle](agent-lifecycle.md) | `curated` | +| [tool execution pipeline](tool-execution-pipeline.md) | `curated` | +| [ACP snapshot replay](acp/snapshot-replay.md) | `curated` | + +Regenerate with `pnpm run gen-doc-graphs`; verify freshness with `pnpm run verify-doc-graphs`. diff --git a/docs/graphs/README.md b/docs/graphs/README.md deleted file mode 100644 index 995481d160..0000000000 --- a/docs/graphs/README.md +++ /dev/null @@ -1,26 +0,0 @@ - - -# Documentation Graph Atlas - -Maintenance mode: mixed: each linked page declares generated, hybrid, or curated mode. - -The graph atlas is the relationship layer above the generated catalogs. Use it to navigate package topology, capability seams, event flow, model-facing tools, and runtime lifecycle paths. Exact signatures and type shapes still live in [cordis-catalog/](../cordis-catalog/events-and-services.md), [tool-catalog/](../tool-catalog/tools.md), and [core-data-structures/](../core-data-structures/core.md). - -The process decision behind this atlas is recorded in [the documentation graph atlas RFC](../rfc/implemented/process/2026-07-03-documentation-graph-atlas.md). - -| Graph | Mode | -| --- | --- | -| [package topology by group](package-topology.md) | `generated` | -| [capability seams and core services](capability-seams.md) | `hybrid generated` | -| [app composition](app-composition.md) | `hybrid generated` | -| [event producer/consumer matrix](event-producer-consumer.md) | `hybrid generated` | -| [tool affordance map](tool-affordance-map.md) | `hybrid generated` | -| [agent turn and step lifecycle](agent-lifecycle.md) | `curated` | -| [tool execution pipeline](tool-execution-pipeline.md) | `curated` | -| [session surface and message projection](session-surface.md) | `curated` | -| [subagent and session lineage](subagent-lineage.md) | `curated` | -| [plugin disposal and hot reload ownership](hot-reload-disposal.md) | `curated` | -| [ACP snapshot replay](snapshot-replay.md) | `curated` | - -Regenerate with `pnpm run gen-doc-graphs`; verify freshness with `pnpm run verify-doc-graphs`. diff --git a/docs/graphs/agent-lifecycle.md b/docs/graphs/agent-lifecycle.md deleted file mode 100644 index aca82a80a0..0000000000 --- a/docs/graphs/agent-lifecycle.md +++ /dev/null @@ -1,49 +0,0 @@ - - -# Agent Turn And Step Lifecycle - -Maintenance mode: curated Mermaid sequence; exact event signatures live in the generated Cordis catalog. - -This sequence is the visual companion to [architecture.md](../architecture.md#loop-lifecycle-session--turn--step). It keeps durable replay facts on `session/event` and live control/status on `agent/*`. - -```mermaid -sequenceDiagram - participant User - participant Agent - participant Driver - participant Hooks as hook listeners - participant Prompt as ctx.systemPrompt - participant LLM as ctx.llm - participant Tools as ctx.tools - participant Session - participant Persistence - participant SDK as UI or SDK listener - User->>Agent: send(content) - Agent-->>SDK: agent/queued - Agent->>Driver: queued work wakes driver - Driver-->>SDK: agent/status running - Driver->>Session: turn/start - Driver->>Hooks: agent/prompt-submit waterfall - Hooks-->>Driver: allow, block, or add context - Driver->>Session: user/message or rejected turn/end - Driver->>Prompt: system-prompt/assemble waterfall - Driver-->>Driver: agent/pre-step serial checkpoint - Driver->>Session: step/start - Driver->>LLM: agent/request waterfall, then llm/stream waterfall - LLM-->>Driver: StreamChunk* - Driver->>Session: assistant/chunk* - Session-->>SDK: session/event assistant/chunk* - Driver->>Hooks: agent/step-result waterfall - Driver->>Session: assistant/message - Driver->>Session: tool/call - Driver->>Tools: execute through pre and post waterfalls - Tools-->>Session: tool-owned events when applicable - Driver->>Session: tool/result and step/end - Driver->>Hooks: agent/turn-continuation waterfall - Driver->>Session: turn/end - Driver->>Persistence: session/flush parallel checkpoint - Driver-->>SDK: agent/status idle -``` - -SDK users that need replayable transcript data should consume `session/event`; `agent/*` is the live coordination surface for queue/status, prompt interception, request shaping, steering, continuation, and errors. diff --git a/docs/graphs/app-composition.md b/docs/graphs/app-composition.md deleted file mode 100644 index ba1ff05a2a..0000000000 --- a/docs/graphs/app-composition.md +++ /dev/null @@ -1,106 +0,0 @@ - - -# App Composition - -Maintenance mode: hybrid: leaf plugin lists are parsed from `examples/*/cordis.yml`; bundle expansions are curated from app package source. - -This graph is for SDK users asking which pieces a runnable agent loads. Leaf configs choose adapters and optional product tools; app packages provide the front door; `dsh-agent-core` bundles the providerless spine. - -```mermaid -flowchart LR - subgraph example_echo["examples/echo-agent"] - cfg_echo["cordis.yml"] - plugin_echo_hmr["hmr
@cordisjs/plugin-hmr"] - cfg_echo --> plugin_echo_hmr - plugin_echo_mock_llm["mock-llm
./src/mock-llm.ts"] - cfg_echo --> plugin_echo_mock_llm - plugin_echo_echo_tool["echo-tool
./src/echo-tool.ts"] - cfg_echo --> plugin_echo_echo_tool - plugin_echo_bash["bash
@deepseek-ai/dsh-bash-local"] - cfg_echo --> plugin_echo_bash - plugin_echo_stdio_agent["stdio-agent
@deepseek-ai/dsh-stdio-agent"] - cfg_echo --> plugin_echo_stdio_agent - plugin_echo_stdio_agent --> bundle_stdio - end - subgraph example_coding["examples/coding-agent"] - cfg_coding["cordis.yml"] - plugin_coding_hmr["hmr
@cordisjs/plugin-hmr"] - cfg_coding --> plugin_coding_hmr - plugin_coding_llm_deepseek["llm-deepseek
@deepseek-ai/dsh-llm-deepseek"] - cfg_coding --> plugin_coding_llm_deepseek - plugin_coding_bash["bash
@deepseek-ai/dsh-bash-local"] - cfg_coding --> plugin_coding_bash - plugin_coding_stdio_agent["stdio-agent
@deepseek-ai/dsh-stdio-agent"] - cfg_coding --> plugin_coding_stdio_agent - plugin_coding_stdio_agent --> bundle_stdio - plugin_coding_compact_basic["compact-basic
@deepseek-ai/dsh-compact-basic"] - cfg_coding --> plugin_coding_compact_basic - plugin_coding_subagent["subagent
@deepseek-ai/dsh-subagent"] - cfg_coding --> plugin_coding_subagent - plugin_coding_subagent_spawn["subagent-spawn
@deepseek-ai/dsh-subagent-spawn"] - cfg_coding --> plugin_coding_subagent_spawn - plugin_coding_subagent_fork["subagent-fork
@deepseek-ai/dsh-subagent-fork"] - cfg_coding --> plugin_coding_subagent_fork - plugin_coding_tool_subagent["tool-subagent
@deepseek-ai/dsh-tool-subagent"] - cfg_coding --> plugin_coding_tool_subagent - plugin_coding_tool_subagent_fork["tool-subagent-fork
@deepseek-ai/dsh-tool-subagent"] - cfg_coding --> plugin_coding_tool_subagent_fork - plugin_coding_tool_todo["tool-todo
@deepseek-ai/dsh-tool-todo"] - cfg_coding --> plugin_coding_tool_todo - plugin_coding_fs_local["fs-local
@deepseek-ai/dsh-fs-local"] - cfg_coding --> plugin_coding_fs_local - plugin_coding_fs_policy["fs-policy
@deepseek-ai/dsh-fs-policy"] - cfg_coding --> plugin_coding_fs_policy - plugin_coding_tool_fs["tool-fs
@deepseek-ai/dsh-tool-fs"] - cfg_coding --> plugin_coding_tool_fs - end - subgraph example_acp["examples/acp-agent"] - cfg_acp["cordis.yml"] - plugin_acp_llm_deepseek["llm-deepseek
@deepseek-ai/dsh-llm-deepseek"] - cfg_acp --> plugin_acp_llm_deepseek - plugin_acp_bash["bash
@deepseek-ai/dsh-bash-local"] - cfg_acp --> plugin_acp_bash - plugin_acp_acp_agent["acp-agent
@deepseek-ai/dsh-acp-agent"] - cfg_acp --> plugin_acp_acp_agent - plugin_acp_acp_agent --> bundle_acp_agent - plugin_acp_subagent["subagent
@deepseek-ai/dsh-subagent"] - cfg_acp --> plugin_acp_subagent - plugin_acp_subagent_spawn["subagent-spawn
@deepseek-ai/dsh-subagent-spawn"] - cfg_acp --> plugin_acp_subagent_spawn - plugin_acp_subagent_fork["subagent-fork
@deepseek-ai/dsh-subagent-fork"] - cfg_acp --> plugin_acp_subagent_fork - plugin_acp_tool_subagent["tool-subagent
@deepseek-ai/dsh-tool-subagent"] - cfg_acp --> plugin_acp_tool_subagent - plugin_acp_tool_subagent_fork["tool-subagent-fork
@deepseek-ai/dsh-tool-subagent"] - cfg_acp --> plugin_acp_tool_subagent_fork - plugin_acp_tool_todo["tool-todo
@deepseek-ai/dsh-tool-todo"] - cfg_acp --> plugin_acp_tool_todo - plugin_acp_fs_local["fs-local
@deepseek-ai/dsh-fs-local"] - cfg_acp --> plugin_acp_fs_local - plugin_acp_fs_policy["fs-policy
@deepseek-ai/dsh-fs-policy"] - cfg_acp --> plugin_acp_fs_policy - plugin_acp_tool_fs["tool-fs
@deepseek-ai/dsh-tool-fs"] - cfg_acp --> plugin_acp_tool_fs - plugin_acp_hooks_claude["hooks-claude
@deepseek-ai/dsh-hooks-claude"] - cfg_acp --> plugin_acp_hooks_claude - plugin_acp_hooks_codex["hooks-codex
@deepseek-ai/dsh-hooks-codex"] - cfg_acp --> plugin_acp_hooks_codex - end - bundle_stdio["@deepseek-ai/dsh-stdio-agent"] --> bundle_agent_core["@deepseek-ai/dsh-agent-core"] - bundle_stdio --> bundle_jsonl["@deepseek-ai/dsh-session-persistence-jsonl"] - bundle_stdio --> bundle_ui_stdio["@deepseek-ai/dsh-ui-stdio"] - bundle_acp_agent["@deepseek-ai/dsh-acp-agent"] --> bundle_agent_core - bundle_acp_agent --> bundle_jsonl - bundle_acp_agent --> bundle_acp["@deepseek-ai/dsh-acp"] - bundle_agent_core --> spine_llm["ctx.llm"] - bundle_agent_core --> spine_sessions["ctx.sessions"] - bundle_agent_core --> spine_tools["ctx.tools + tool-bash"] - bundle_agent_core --> spine_loop["ctx.agents + ctx.agentLoop"] -``` - -| Example | Parsed plugin ids | Config | -| --- | --- | --- | -| `examples/echo-agent` | `hmr`, `mock-llm`, `echo-tool`, `bash`, `stdio-agent` | [`examples/echo-agent/cordis.yml`](../../examples/echo-agent/cordis.yml) | -| `examples/coding-agent` | `hmr`, `llm-deepseek`, `bash`, `stdio-agent`, `compact-basic`, `subagent`, `subagent-spawn`, `subagent-fork`, `tool-subagent`, `tool-subagent-fork`, `tool-todo`, `fs-local`, `fs-policy`, `tool-fs` | [`examples/coding-agent/cordis.yml`](../../examples/coding-agent/cordis.yml) | -| `examples/acp-agent` | `llm-deepseek`, `bash`, `acp-agent`, `subagent`, `subagent-spawn`, `subagent-fork`, `tool-subagent`, `tool-subagent-fork`, `tool-todo`, `fs-local`, `fs-policy`, `tool-fs`, `hooks-claude`, `hooks-codex` | [`examples/acp-agent/cordis.yml`](../../examples/acp-agent/cordis.yml) | diff --git a/docs/graphs/event-producer-consumer.md b/docs/graphs/event-producer-consumer.md deleted file mode 100644 index 885086534b..0000000000 --- a/docs/graphs/event-producer-consumer.md +++ /dev/null @@ -1,38 +0,0 @@ - - -# Event Producer And Consumer Matrix - -Maintenance mode: hybrid generated: Cordis event declarations and most producer/listener edges are AST-scanned; dynamic dispatch sites are classified in `scripts/gen-doc-graphs.ts`. - -This matrix shows which packages dispatch each harness-owned event and which packages listen to it. It is intentionally a table rather than one large graph: events are many-to-many, and dense relation data is easier to review in rows. Dynamic dispatch overrides cover sites that deliberately bypass `ctx.emit`, such as subagent lifecycle containment. - -| Event | Mode | Declared in | Dispatchers | Listeners | -| --- | --- | --- | --- | --- | -| `agent/created` | `emit` | [`packages/core/agent/src/types.ts:234`](../../packages/core/agent/src/types.ts) | [`agent`](../../packages/core/agent) (`emit`) | [`stdio-agent`](../../packages/ui/stdio-agent) | -| `agent/disposed` | `emit` | [`packages/core/agent/src/types.ts:241`](../../packages/core/agent/src/types.ts) | [`agent`](../../packages/core/agent) (`emit`) | [`stdio-agent`](../../packages/ui/stdio-agent) | -| `agent/error` | `emit` | [`packages/core/agent/src/types.ts:389`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`emit`) | - | -| `agent/pre-step` | `serial` | [`packages/core/agent/src/types.ts:319`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`serial`) | [`compact-basic`](../../packages/compact/compact-basic) | -| `agent/prompt-submit` | `waterfall` | [`packages/core/agent/src/types.ts:332`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`waterfall`) | [`hooks-claude`](../../packages/hooks/hooks-claude), [`hooks-codex`](../../packages/hooks/hooks-codex) | -| `agent/queued` | `emit` | [`packages/core/agent/src/types.ts:259`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`emit`) | - | -| `agent/request` | `waterfall` | [`packages/core/agent/src/types.ts:345`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`waterfall`), [`compact-basic`](../../packages/compact/compact-basic) (`waterfall`) | - | -| `agent/session-start` | `emit` | [`packages/core/agent/src/types.ts:274`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`emit`) | [`hooks-claude`](../../packages/hooks/hooks-claude), [`hooks-codex`](../../packages/hooks/hooks-codex) | -| `agent/status` | `emit` | [`packages/core/agent/src/types.ts:250`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`emit`) | [`acp`](../../packages/ui/acp), [`invariants`](../../packages/support/invariants), [`stdio-agent`](../../packages/ui/stdio-agent) | -| `agent/steering` | `emit` | [`packages/core/agent/src/types.ts:379`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`emit`) | - | -| `agent/step-result` | `waterfall` | [`packages/core/agent/src/types.ts:355`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`waterfall`) | - | -| `agent/turn-continuation` | `waterfall` | [`packages/core/agent/src/types.ts:368`](../../packages/core/agent/src/types.ts) | [`agent-loop`](../../packages/core/agent-loop) (`waterfall`) | [`hooks-claude`](../../packages/hooks/hooks-claude), [`hooks-codex`](../../packages/hooks/hooks-codex) | -| `fs/edit-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:123`](../../packages/fs/fs/src/index.ts) | [`tool-fs`](../../packages/fs/tool-fs) (`waterfall`) | [`fs-policy`](../../packages/fs/fs-policy) | -| `fs/observed` | `emit` | [`packages/fs/fs/src/index.ts:138`](../../packages/fs/fs/src/index.ts) | [`tool-fs`](../../packages/fs/tool-fs) (`emit`) | [`fs-policy`](../../packages/fs/fs-policy) | -| `fs/write-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:109`](../../packages/fs/fs/src/index.ts) | [`tool-fs`](../../packages/fs/tool-fs) (`waterfall`) | [`fs-policy`](../../packages/fs/fs-policy) | -| `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:32`](../../packages/llm/llm/src/index.ts) | [`llm`](../../packages/llm/llm) (`waterfall`) | [`llm-replay`](../../packages/support/llm-replay) | -| `session/created` | `emit` | [`packages/core/session/src/index.ts:36`](../../packages/core/session/src/index.ts) | [`session`](../../packages/core/session) (`emit`) | [`invariants`](../../packages/support/invariants), [`session-persistence`](../../packages/session-persistence/session-persistence) | -| `session/event` | `emit` | [`packages/core/session/src/index.ts:44`](../../packages/core/session/src/index.ts) | [`session`](../../packages/core/session) (`emit`) | [`acp`](../../packages/ui/acp), [`invariants`](../../packages/support/invariants), [`session-persistence`](../../packages/session-persistence/session-persistence), [`stdio-agent`](../../packages/ui/stdio-agent) | -| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:54`](../../packages/core/session/src/index.ts) | [`agent-loop`](../../packages/core/agent-loop) (`parallel`) | [`session-persistence`](../../packages/session-persistence/session-persistence) | -| `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:77`](../../packages/subagent/subagent/src/index.ts) | [`subagent`](../../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../../packages/hooks/hooks-claude) | -| `subagent/start` | `emit` | [`packages/subagent/subagent/src/index.ts:70`](../../packages/subagent/subagent/src/index.ts) | [`subagent`](../../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../../packages/hooks/hooks-claude) | -| `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:26`](../../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../../packages/core/system-prompt) (`waterfall`) | - | -| `system-prompt/change` | `emit` | [`packages/core/system-prompt/src/index.ts:32`](../../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../../packages/core/system-prompt) (`emit`) | - | -| `tools/change` | `emit` | [`packages/core/tools/src/index.ts:87`](../../packages/core/tools/src/index.ts) | [`tools`](../../packages/core/tools) (`emit`) | - | -| `tools/post-execute` | `waterfall` | [`packages/core/tools/src/index.ts:82`](../../packages/core/tools/src/index.ts) | [`tools`](../../packages/core/tools) (`waterfall`) | [`hooks-claude`](../../packages/hooks/hooks-claude), [`hooks-codex`](../../packages/hooks/hooks-codex) | -| `tools/pre-execute` | `waterfall` | [`packages/core/tools/src/index.ts:66`](../../packages/core/tools/src/index.ts) | [`tools`](../../packages/core/tools) (`waterfall`) | [`hooks-claude`](../../packages/hooks/hooks-claude), [`hooks-codex`](../../packages/hooks/hooks-codex) | -| `web/providers-change` | `emit` | [`packages/web/web/src/index.ts:65`](../../packages/web/web/src/index.ts) | [`web`](../../packages/web/web) (`emit`) | - | diff --git a/docs/graphs/hot-reload-disposal.md b/docs/graphs/hot-reload-disposal.md deleted file mode 100644 index ca98c25692..0000000000 --- a/docs/graphs/hot-reload-disposal.md +++ /dev/null @@ -1,24 +0,0 @@ - - -# Plugin Disposal And Hot Reload Ownership - -Maintenance mode: curated Mermaid flow based on Cordis fiber/effect conventions. - -This graph is a maintainer checklist for plugin authors: registrations are effects, service injection gates activation, and owned handles must be disposed by their owner. - -```mermaid -flowchart TD - plugin["ctx.plugin(plugin) creates fiber"] - inject["static inject gates activation"] - service["ctx.provide / Service constructor"] - effects["ctx.effect registrations
events, tools, adapters, timers"] - reload["HMR / fiber.dispose()"] - disposers["Run disposers in owner fiber"] - quiescence["Owned AgentHandle.dispose()
or service teardown awaits quiescence"] - plugin --> inject --> service - inject --> effects - reload --> disposers --> quiescence -``` - -Hook bridges and SDK plugins increase the number of long-lived listeners, so this ownership graph should stay small and visible. diff --git a/docs/graphs/package-topology.md b/docs/graphs/package-topology.md deleted file mode 100644 index 04fadd1438..0000000000 --- a/docs/graphs/package-topology.md +++ /dev/null @@ -1,247 +0,0 @@ - - -# Package Topology By Group - -Maintenance mode: generated from `packages/*/*/package.json` peer dependencies plus package group paths. - -This graph complements [module-graph.md](../module-graph.md): it keeps the same canonical peer-dependency edge source, but clusters packages by the `packages//` hierarchy so layering and capability families are easier to scan. - -```mermaid -flowchart TD - subgraph group_util["packages/util"] - pkg_brand["brand"] - end - subgraph group_llm["packages/llm"] - pkg_llm["llm"] - pkg_llm_deepseek["llm-deepseek"] - pkg_llm_pi_ai["llm-pi-ai"] - end - subgraph group_core["packages/core"] - pkg_agent["agent"] - pkg_agent_core["agent-core"] - pkg_agent_loop["agent-loop"] - pkg_session["session"] - pkg_system_prompt["system-prompt"] - pkg_tools["tools"] - end - subgraph group_bash["packages/bash"] - pkg_bash["bash"] - pkg_bash_local["bash-local"] - pkg_tool_bash["tool-bash"] - end - subgraph group_fs["packages/fs"] - pkg_fs["fs"] - pkg_fs_local["fs-local"] - pkg_fs_policy["fs-policy"] - pkg_tool_fs["tool-fs"] - end - subgraph group_compact["packages/compact"] - pkg_compact["compact"] - pkg_compact_basic["compact-basic"] - end - subgraph group_subagent["packages/subagent"] - pkg_subagent["subagent"] - pkg_subagent_acp["subagent-acp"] - pkg_subagent_fork["subagent-fork"] - pkg_subagent_inprocess["subagent-inprocess"] - pkg_subagent_spawn["subagent-spawn"] - pkg_tool_subagent["tool-subagent"] - end - subgraph group_web["packages/web"] - pkg_tool_web["tool-web"] - pkg_web["web"] - pkg_web_fetch_local["web-fetch-local"] - pkg_web_search_deepseek["web-search-deepseek"] - pkg_web_search_exa["web-search-exa"] - pkg_web_search_perplexity["web-search-perplexity"] - end - subgraph group_todo["packages/todo"] - pkg_tool_todo["tool-todo"] - end - subgraph group_hooks["packages/hooks"] - pkg_hook_protocol["hook-protocol"] - pkg_hooks_claude["hooks-claude"] - pkg_hooks_codex["hooks-codex"] - end - subgraph group_session_persistence["packages/session-persistence"] - pkg_session_persistence["session-persistence"] - pkg_session_persistence_jsonl["session-persistence-jsonl"] - pkg_session_persistence_sqlite["session-persistence-sqlite"] - end - subgraph group_support["packages/support"] - pkg_invariants["invariants"] - pkg_llm_replay["llm-replay"] - pkg_subagent_mock["subagent-mock"] - end - subgraph group_ui["packages/ui"] - pkg_acp["acp"] - pkg_acp_agent["acp-agent"] - pkg_stdio_agent["stdio-agent"] - end - pkg_llm --> pkg_brand - pkg_bash --> pkg_brand - pkg_llm_deepseek --> pkg_llm - pkg_llm_pi_ai --> pkg_llm - pkg_session --> pkg_brand - pkg_session --> pkg_llm - pkg_system_prompt --> pkg_llm - pkg_bash_local --> pkg_bash - pkg_fs --> pkg_brand - pkg_fs --> pkg_llm - pkg_web --> pkg_llm - pkg_agent --> pkg_brand - pkg_agent --> pkg_llm - pkg_agent --> pkg_session - pkg_fs_local --> pkg_fs - pkg_fs_policy --> pkg_fs - pkg_compact --> pkg_llm - pkg_compact --> pkg_session - pkg_web_fetch_local --> pkg_web - pkg_web_search_deepseek --> pkg_web - pkg_web_search_exa --> pkg_web - pkg_web_search_perplexity --> pkg_web - pkg_hook_protocol --> pkg_bash - pkg_hook_protocol --> pkg_session - pkg_session_persistence --> pkg_session - pkg_llm_replay --> pkg_llm - pkg_llm_replay --> pkg_session - pkg_tools --> pkg_agent - pkg_tools --> pkg_llm - pkg_tools --> pkg_system_prompt - pkg_compact_basic --> pkg_agent - pkg_compact_basic --> pkg_compact - pkg_compact_basic --> pkg_llm - pkg_compact_basic --> pkg_session - pkg_session_persistence_jsonl --> pkg_session - pkg_session_persistence_jsonl --> pkg_session_persistence - pkg_session_persistence_sqlite --> pkg_session - pkg_session_persistence_sqlite --> pkg_session_persistence - pkg_invariants --> pkg_agent - pkg_invariants --> pkg_llm - pkg_invariants --> pkg_session - pkg_agent_loop --> pkg_agent - pkg_agent_loop --> pkg_llm - pkg_agent_loop --> pkg_session - pkg_agent_loop --> pkg_session_persistence - pkg_agent_loop --> pkg_system_prompt - pkg_agent_loop --> pkg_tools - pkg_tool_bash --> pkg_agent - pkg_tool_bash --> pkg_bash - pkg_tool_bash --> pkg_llm - pkg_tool_bash --> pkg_tools - pkg_tool_fs --> pkg_fs - pkg_tool_fs --> pkg_llm - pkg_tool_fs --> pkg_session - pkg_tool_fs --> pkg_system_prompt - pkg_tool_fs --> pkg_tools - pkg_subagent --> pkg_agent - pkg_subagent --> pkg_llm - pkg_subagent --> pkg_tools - pkg_tool_web --> pkg_llm - pkg_tool_web --> pkg_system_prompt - pkg_tool_web --> pkg_tools - pkg_tool_web --> pkg_web - pkg_tool_todo --> pkg_agent - pkg_tool_todo --> pkg_session - pkg_tool_todo --> pkg_tools - pkg_hooks_codex --> pkg_agent - pkg_hooks_codex --> pkg_hook_protocol - pkg_hooks_codex --> pkg_llm - pkg_hooks_codex --> pkg_session - pkg_hooks_codex --> pkg_tools - pkg_acp --> pkg_agent - pkg_acp --> pkg_llm - pkg_acp --> pkg_session - pkg_acp --> pkg_session_persistence - pkg_acp --> pkg_tools - pkg_agent_core --> pkg_agent - pkg_agent_core --> pkg_agent_loop - pkg_agent_core --> pkg_invariants - pkg_agent_core --> pkg_llm - pkg_agent_core --> pkg_session - pkg_agent_core --> pkg_system_prompt - pkg_agent_core --> pkg_tool_bash - pkg_agent_core --> pkg_tools - pkg_subagent_acp --> pkg_agent - pkg_subagent_acp --> pkg_llm - pkg_subagent_acp --> pkg_subagent - pkg_subagent_inprocess --> pkg_agent - pkg_subagent_inprocess --> pkg_llm - pkg_subagent_inprocess --> pkg_session - pkg_subagent_inprocess --> pkg_subagent - pkg_tool_subagent --> pkg_agent - pkg_tool_subagent --> pkg_llm - pkg_tool_subagent --> pkg_subagent - pkg_tool_subagent --> pkg_tools - pkg_hooks_claude --> pkg_agent - pkg_hooks_claude --> pkg_hook_protocol - pkg_hooks_claude --> pkg_llm - pkg_hooks_claude --> pkg_session - pkg_hooks_claude --> pkg_subagent - pkg_hooks_claude --> pkg_tools - pkg_subagent_mock --> pkg_agent - pkg_subagent_mock --> pkg_llm - pkg_subagent_mock --> pkg_subagent - pkg_subagent_fork --> pkg_agent - pkg_subagent_fork --> pkg_session - pkg_subagent_fork --> pkg_subagent - pkg_subagent_fork --> pkg_subagent_inprocess - pkg_subagent_spawn --> pkg_subagent - pkg_subagent_spawn --> pkg_subagent_inprocess - pkg_acp_agent --> pkg_acp - pkg_acp_agent --> pkg_agent_core - pkg_acp_agent --> pkg_session_persistence_jsonl - pkg_stdio_agent --> pkg_agent - pkg_stdio_agent --> pkg_agent_core - pkg_stdio_agent --> pkg_llm - pkg_stdio_agent --> pkg_session - pkg_stdio_agent --> pkg_session_persistence_jsonl -``` - -| Package | Group | Depends on | -| --- | --- | --- | -| [`brand`](../../packages/util/brand) | `util` | - | -| [`llm`](../../packages/llm/llm) | `llm` | [`brand`](../../packages/util/brand) | -| [`bash`](../../packages/bash/bash) | `bash` | [`brand`](../../packages/util/brand) | -| [`llm-deepseek`](../../packages/llm/llm-deepseek) | `llm` | [`llm`](../../packages/llm/llm) | -| [`llm-pi-ai`](../../packages/llm/llm-pi-ai) | `llm` | [`llm`](../../packages/llm/llm) | -| [`session`](../../packages/core/session) | `core` | [`brand`](../../packages/util/brand), [`llm`](../../packages/llm/llm) | -| [`system-prompt`](../../packages/core/system-prompt) | `core` | [`llm`](../../packages/llm/llm) | -| [`bash-local`](../../packages/bash/bash-local) | `bash` | [`bash`](../../packages/bash/bash) | -| [`fs`](../../packages/fs/fs) | `fs` | [`brand`](../../packages/util/brand), [`llm`](../../packages/llm/llm) | -| [`web`](../../packages/web/web) | `web` | [`llm`](../../packages/llm/llm) | -| [`agent`](../../packages/core/agent) | `core` | [`brand`](../../packages/util/brand), [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session) | -| [`fs-local`](../../packages/fs/fs-local) | `fs` | [`fs`](../../packages/fs/fs) | -| [`fs-policy`](../../packages/fs/fs-policy) | `fs` | [`fs`](../../packages/fs/fs) | -| [`compact`](../../packages/compact/compact) | `compact` | [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session) | -| [`web-fetch-local`](../../packages/web/web-fetch-local) | `web` | [`web`](../../packages/web/web) | -| [`web-search-deepseek`](../../packages/web/web-search-deepseek) | `web` | [`web`](../../packages/web/web) | -| [`web-search-exa`](../../packages/web/web-search-exa) | `web` | [`web`](../../packages/web/web) | -| [`web-search-perplexity`](../../packages/web/web-search-perplexity) | `web` | [`web`](../../packages/web/web) | -| [`hook-protocol`](../../packages/hooks/hook-protocol) | `hooks` | [`bash`](../../packages/bash/bash), [`session`](../../packages/core/session) | -| [`session-persistence`](../../packages/session-persistence/session-persistence) | `session-persistence` | [`session`](../../packages/core/session) | -| [`llm-replay`](../../packages/support/llm-replay) | `support` | [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session) | -| [`tools`](../../packages/core/tools) | `core` | [`agent`](../../packages/core/agent), [`llm`](../../packages/llm/llm), [`system-prompt`](../../packages/core/system-prompt) | -| [`compact-basic`](../../packages/compact/compact-basic) | `compact` | [`agent`](../../packages/core/agent), [`compact`](../../packages/compact/compact), [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session) | -| [`session-persistence-jsonl`](../../packages/session-persistence/session-persistence-jsonl) | `session-persistence` | [`session`](../../packages/core/session), [`session-persistence`](../../packages/session-persistence/session-persistence) | -| [`session-persistence-sqlite`](../../packages/session-persistence/session-persistence-sqlite) | `session-persistence` | [`session`](../../packages/core/session), [`session-persistence`](../../packages/session-persistence/session-persistence) | -| [`invariants`](../../packages/support/invariants) | `support` | [`agent`](../../packages/core/agent), [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session) | -| [`agent-loop`](../../packages/core/agent-loop) | `core` | [`agent`](../../packages/core/agent), [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session), [`session-persistence`](../../packages/session-persistence/session-persistence), [`system-prompt`](../../packages/core/system-prompt), [`tools`](../../packages/core/tools) | -| [`tool-bash`](../../packages/bash/tool-bash) | `bash` | [`agent`](../../packages/core/agent), [`bash`](../../packages/bash/bash), [`llm`](../../packages/llm/llm), [`tools`](../../packages/core/tools) | -| [`tool-fs`](../../packages/fs/tool-fs) | `fs` | [`fs`](../../packages/fs/fs), [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session), [`system-prompt`](../../packages/core/system-prompt), [`tools`](../../packages/core/tools) | -| [`subagent`](../../packages/subagent/subagent) | `subagent` | [`agent`](../../packages/core/agent), [`llm`](../../packages/llm/llm), [`tools`](../../packages/core/tools) | -| [`tool-web`](../../packages/web/tool-web) | `web` | [`llm`](../../packages/llm/llm), [`system-prompt`](../../packages/core/system-prompt), [`tools`](../../packages/core/tools), [`web`](../../packages/web/web) | -| [`tool-todo`](../../packages/todo/tool-todo) | `todo` | [`agent`](../../packages/core/agent), [`session`](../../packages/core/session), [`tools`](../../packages/core/tools) | -| [`hooks-codex`](../../packages/hooks/hooks-codex) | `hooks` | [`agent`](../../packages/core/agent), [`hook-protocol`](../../packages/hooks/hook-protocol), [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session), [`tools`](../../packages/core/tools) | -| [`acp`](../../packages/ui/acp) | `ui` | [`agent`](../../packages/core/agent), [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session), [`session-persistence`](../../packages/session-persistence/session-persistence), [`tools`](../../packages/core/tools) | -| [`agent-core`](../../packages/core/agent-core) | `core` | [`agent`](../../packages/core/agent), [`agent-loop`](../../packages/core/agent-loop), [`invariants`](../../packages/support/invariants), [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session), [`system-prompt`](../../packages/core/system-prompt), [`tool-bash`](../../packages/bash/tool-bash), [`tools`](../../packages/core/tools) | -| [`subagent-acp`](../../packages/subagent/subagent-acp) | `subagent` | [`agent`](../../packages/core/agent), [`llm`](../../packages/llm/llm), [`subagent`](../../packages/subagent/subagent) | -| [`subagent-inprocess`](../../packages/subagent/subagent-inprocess) | `subagent` | [`agent`](../../packages/core/agent), [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session), [`subagent`](../../packages/subagent/subagent) | -| [`tool-subagent`](../../packages/subagent/tool-subagent) | `subagent` | [`agent`](../../packages/core/agent), [`llm`](../../packages/llm/llm), [`subagent`](../../packages/subagent/subagent), [`tools`](../../packages/core/tools) | -| [`hooks-claude`](../../packages/hooks/hooks-claude) | `hooks` | [`agent`](../../packages/core/agent), [`hook-protocol`](../../packages/hooks/hook-protocol), [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session), [`subagent`](../../packages/subagent/subagent), [`tools`](../../packages/core/tools) | -| [`subagent-mock`](../../packages/support/subagent-mock) | `support` | [`agent`](../../packages/core/agent), [`llm`](../../packages/llm/llm), [`subagent`](../../packages/subagent/subagent) | -| [`subagent-fork`](../../packages/subagent/subagent-fork) | `subagent` | [`agent`](../../packages/core/agent), [`session`](../../packages/core/session), [`subagent`](../../packages/subagent/subagent), [`subagent-inprocess`](../../packages/subagent/subagent-inprocess) | -| [`subagent-spawn`](../../packages/subagent/subagent-spawn) | `subagent` | [`subagent`](../../packages/subagent/subagent), [`subagent-inprocess`](../../packages/subagent/subagent-inprocess) | -| [`acp-agent`](../../packages/ui/acp-agent) | `ui` | [`acp`](../../packages/ui/acp), [`agent-core`](../../packages/core/agent-core), [`session-persistence-jsonl`](../../packages/session-persistence/session-persistence-jsonl) | -| [`stdio-agent`](../../packages/ui/stdio-agent) | `ui` | [`agent`](../../packages/core/agent), [`agent-core`](../../packages/core/agent-core), [`llm`](../../packages/llm/llm), [`session`](../../packages/core/session), [`session-persistence-jsonl`](../../packages/session-persistence/session-persistence-jsonl) | diff --git a/docs/graphs/session-surface.md b/docs/graphs/session-surface.md deleted file mode 100644 index b25f85b2d2..0000000000 --- a/docs/graphs/session-surface.md +++ /dev/null @@ -1,25 +0,0 @@ - - -# Session Surface And Message Projection - -Maintenance mode: curated Mermaid dataflow; exact event/type shapes live in core-data-structures. - -This graph separates the append-only log from the derived message surface the next model request sees. - -```mermaid -flowchart LR - append["Session.append(type, data)"] - log["Append-only SessionEvent log"] - surface["SurfaceManager linked list
surfaceOp + sourceEventSeqs"] - derive["deriveMessages()"] - model["GenerateOptions.messages"] - persist["JSONL / SQLite persistence"] - replay["load / replay / fork seed"] - append --> log - log --> surface - surface --> derive --> model - log --> persist --> replay --> log -``` - -See [core-data-structures/session.md](../core-data-structures/session.md) for the full `SessionEventMap`, surface operations, and turn-enclosure invariant. diff --git a/docs/graphs/subagent-lineage.md b/docs/graphs/subagent-lineage.md deleted file mode 100644 index f687405782..0000000000 --- a/docs/graphs/subagent-lineage.md +++ /dev/null @@ -1,27 +0,0 @@ - - -# Subagent And Session Lineage - -Maintenance mode: curated Mermaid flow; provider inventory is visible in the generated capability seam graph. - -This graph keeps delegation semantics separate from hook observation. A subagent backend creates an ordinary child agent/session through the shared provider registry. - -```mermaid -flowchart TD - parent["Parent Agent + Session"] - tool["tool-subagent
model-facing name"] - registry["ctx.subagents provider registry"] - spawn["spawn provider
fresh child session"] - fork["fork provider
seeded from completed-turn prefix"] - acp["ACP provider
out-of-process child"] - child["Child AgentHandle
ordinary Agent lifecycle"] - result["SubagentResult returned to tool"] - parent --> tool --> registry - registry --> spawn --> child - registry --> fork --> child - registry --> acp --> child - child --> result --> parent -``` - -The hooks stack adds richer lifecycle observation around child runs; the core ownership rule stays the same: the provider owns the child handle and must dispose it. diff --git a/docs/graphs/tool-affordance-map.md b/docs/graphs/tool-affordance-map.md deleted file mode 100644 index 58632da390..0000000000 --- a/docs/graphs/tool-affordance-map.md +++ /dev/null @@ -1,50 +0,0 @@ - - -# Tool Affordance Map - -Maintenance mode: hybrid: tool names/schemas are boot-harvested from shipped tool plugins; required services and shipped aliases are classified in `scripts/gen-doc-graphs.ts` with a completeness guard. - -This page connects the model-visible tools to the plugin packages and service seams behind them. For exact JSON Schemas, see [tool-catalog/tools.md](../tool-catalog/tools.md). - -```mermaid -flowchart LR - model["Model request tools[]"] - toolpkg__deepseek_ai_dsh_tool_bash["tool-bash
bash, bash_kill, bash_output"] - model --> toolpkg__deepseek_ai_dsh_tool_bash - requires_ctx_tools["ctx.tools"] - toolpkg__deepseek_ai_dsh_tool_bash --> requires_ctx_tools - requires_ctx_bash["ctx.bash"] - toolpkg__deepseek_ai_dsh_tool_bash --> requires_ctx_bash - toolpkg__deepseek_ai_dsh_tool_fs["tool-fs
edit, read, write"] - model --> toolpkg__deepseek_ai_dsh_tool_fs - toolpkg__deepseek_ai_dsh_tool_fs --> requires_ctx_tools - requires_ctx_fs["ctx.fs"] - toolpkg__deepseek_ai_dsh_tool_fs --> requires_ctx_fs - requires_ctx_systemPrompt["ctx.systemPrompt"] - toolpkg__deepseek_ai_dsh_tool_fs --> requires_ctx_systemPrompt - toolpkg__deepseek_ai_dsh_tool_subagent["tool-subagent
subagent"] - model --> toolpkg__deepseek_ai_dsh_tool_subagent - toolpkg__deepseek_ai_dsh_tool_subagent --> requires_ctx_tools - requires_ctx_subagents["ctx.subagents"] - toolpkg__deepseek_ai_dsh_tool_subagent --> requires_ctx_subagents - toolpkg__deepseek_ai_dsh_tool_todo["tool-todo
todo_write"] - model --> toolpkg__deepseek_ai_dsh_tool_todo - toolpkg__deepseek_ai_dsh_tool_todo --> requires_ctx_tools - requires_owning_Agent_session["owning Agent session"] - toolpkg__deepseek_ai_dsh_tool_todo --> requires_owning_Agent_session - toolpkg__deepseek_ai_dsh_tool_web["tool-web
web_fetch, web_search"] - model --> toolpkg__deepseek_ai_dsh_tool_web - toolpkg__deepseek_ai_dsh_tool_web --> requires_ctx_tools - requires_ctx_web["ctx.web"] - toolpkg__deepseek_ai_dsh_tool_web --> requires_ctx_web - toolpkg__deepseek_ai_dsh_tool_web --> requires_ctx_systemPrompt -``` - -| Tool package | Model-visible names | Requires | Writes / affects | Shipped aliases | Note | -| --- | --- | --- | --- | --- | --- | -| `@deepseek-ai/dsh-tool-bash` | `bash`, `bash_kill`, `bash_output` | `ctx.tools`, `ctx.bash` | `tool/call`, `tool/result`, `context/message via agent.inject() for background completion notices` | - | The bash/bash_output/bash_kill tools are model-facing consumers of the bash executor seam. | -| `@deepseek-ai/dsh-tool-fs` | `edit`, `read`, `write` | `ctx.tools`, `ctx.fs`, `ctx.systemPrompt` | `tool/call`, `fs/write-intent or fs/edit-intent for mutations`, `fs/observed after successful file operations`, `tool/result` | - | read/write/edit are the model-facing filesystem tools; read windowing lives here, while read-before-edit policy is supplied by fs-policy through fs/* events. | -| `@deepseek-ai/dsh-tool-subagent` | `subagent` | `ctx.tools`, `ctx.subagents` | `tool/call`, `tool/result`, `child session events through the chosen provider` | `subagent`, `subagent_fork` | The default package schema registers subagent; shipped coding/acp configs load it twice to expose spawn and fork backends. | -| `@deepseek-ai/dsh-tool-todo` | `todo_write` | `ctx.tools`, `owning Agent session` | `tool/call`, `todo/write`, `tool/result` | - | todo_write is session-owned state; UIs render the latest todo/write event as a checklist or ACP plan. | -| `@deepseek-ai/dsh-tool-web` | `web_fetch`, `web_search` | `ctx.tools`, `ctx.web`, `ctx.systemPrompt` | `tool/call`, `tool/result` | - | web_search and web_fetch keep provider selection behind ctx.web so model-visible schemas stay stable across backend swaps. | diff --git a/docs/module-graph.md b/docs/module-graph.md index b2f86332aa..c38baf0c1a 100644 --- a/docs/module-graph.md +++ b/docs/module-graph.md @@ -3,173 +3,243 @@ # Module dependency graph -Inter-package dependencies among the `@deepseek-ai/dsh-*` harness packages, derived from each package's `peerDependencies` (the canonical runtime-dependency signal). An edge `a --> b` means package `a` depends on package `b`. Names have the `@deepseek-ai/dsh-` prefix stripped. +Inter-package dependencies among the `@deepseek-ai/dsh-*` harness packages, derived from each package's `peerDependencies` (the canonical runtime-dependency signal) and grouped by the `packages//` hierarchy. An edge `a --> b` means package `a` depends on package `b`. Names have the `@deepseek-ai/dsh-` prefix stripped. ```mermaid -graph TD - bash --> brand - llm --> brand - bash-local --> bash - fs --> brand - fs --> llm - llm-deepseek --> llm - llm-pi-ai --> llm - session --> brand - session --> llm - system-prompt --> llm - web --> llm - agent --> brand - agent --> llm - agent --> session - compact --> llm - compact --> session - fs-local --> fs - fs-policy --> fs - hook-protocol --> bash - hook-protocol --> session - llm-replay --> llm - llm-replay --> session - session-persistence --> session - web-fetch-local --> web - web-search-deepseek --> web - web-search-exa --> web - web-search-perplexity --> web - compact-basic --> agent - compact-basic --> compact - compact-basic --> llm - compact-basic --> session - invariants --> agent - invariants --> llm - invariants --> session - session-persistence-jsonl --> session - session-persistence-jsonl --> session-persistence - session-persistence-sqlite --> session - session-persistence-sqlite --> session-persistence - tools --> agent - tools --> llm - tools --> system-prompt - acp --> agent - acp --> llm - acp --> session - acp --> session-persistence - acp --> tools - agent-loop --> agent - agent-loop --> llm - agent-loop --> session - agent-loop --> session-persistence - agent-loop --> system-prompt - agent-loop --> tools - hooks-codex --> agent - hooks-codex --> hook-protocol - hooks-codex --> llm - hooks-codex --> session - hooks-codex --> tools - subagent --> agent - subagent --> llm - subagent --> tools - tool-bash --> agent - tool-bash --> bash - tool-bash --> llm - tool-bash --> tools - tool-fs --> fs - tool-fs --> llm - tool-fs --> session - tool-fs --> system-prompt - tool-fs --> tools - tool-todo --> agent - tool-todo --> session - tool-todo --> tools - tool-web --> llm - tool-web --> system-prompt - tool-web --> tools - tool-web --> web - agent-core --> agent - agent-core --> agent-loop - agent-core --> invariants - agent-core --> llm - agent-core --> session - agent-core --> system-prompt - agent-core --> tool-bash - agent-core --> tools - hooks-claude --> agent - hooks-claude --> hook-protocol - hooks-claude --> llm - hooks-claude --> session - hooks-claude --> subagent - hooks-claude --> tools - subagent-acp --> agent - subagent-acp --> llm - subagent-acp --> subagent - subagent-inprocess --> agent - subagent-inprocess --> llm - subagent-inprocess --> session - subagent-inprocess --> subagent - subagent-mock --> agent - subagent-mock --> llm - subagent-mock --> subagent - tool-subagent --> agent - tool-subagent --> llm - tool-subagent --> subagent - tool-subagent --> tools - acp-agent --> acp - acp-agent --> agent-core - acp-agent --> session-persistence-jsonl - stdio-agent --> agent - stdio-agent --> agent-core - stdio-agent --> llm - stdio-agent --> session - stdio-agent --> session-persistence-jsonl - subagent-fork --> agent - subagent-fork --> session - subagent-fork --> subagent - subagent-fork --> subagent-inprocess - subagent-spawn --> subagent - subagent-spawn --> subagent-inprocess +flowchart TD + subgraph group_util["packages/util"] + pkg_brand["brand"] + end + subgraph group_llm["packages/llm"] + pkg_llm["llm"] + pkg_llm_deepseek["llm-deepseek"] + pkg_llm_pi_ai["llm-pi-ai"] + end + subgraph group_core["packages/core"] + pkg_agent["agent"] + pkg_agent_core["agent-core"] + pkg_agent_loop["agent-loop"] + pkg_session["session"] + pkg_system_prompt["system-prompt"] + pkg_tools["tools"] + end + subgraph group_bash["packages/bash"] + pkg_bash["bash"] + pkg_bash_local["bash-local"] + pkg_tool_bash["tool-bash"] + end + subgraph group_fs["packages/fs"] + pkg_fs["fs"] + pkg_fs_local["fs-local"] + pkg_fs_policy["fs-policy"] + pkg_tool_fs["tool-fs"] + end + subgraph group_compact["packages/compact"] + pkg_compact["compact"] + pkg_compact_basic["compact-basic"] + end + subgraph group_subagent["packages/subagent"] + pkg_subagent["subagent"] + pkg_subagent_acp["subagent-acp"] + pkg_subagent_fork["subagent-fork"] + pkg_subagent_inprocess["subagent-inprocess"] + pkg_subagent_spawn["subagent-spawn"] + pkg_tool_subagent["tool-subagent"] + end + subgraph group_web["packages/web"] + pkg_tool_web["tool-web"] + pkg_web["web"] + pkg_web_fetch_local["web-fetch-local"] + pkg_web_search_deepseek["web-search-deepseek"] + pkg_web_search_exa["web-search-exa"] + pkg_web_search_perplexity["web-search-perplexity"] + end + subgraph group_todo["packages/todo"] + pkg_tool_todo["tool-todo"] + end + subgraph group_hooks["packages/hooks"] + pkg_hook_protocol["hook-protocol"] + pkg_hooks_claude["hooks-claude"] + pkg_hooks_codex["hooks-codex"] + end + subgraph group_session_persistence["packages/session-persistence"] + pkg_session_persistence["session-persistence"] + pkg_session_persistence_jsonl["session-persistence-jsonl"] + pkg_session_persistence_sqlite["session-persistence-sqlite"] + end + subgraph group_support["packages/support"] + pkg_invariants["invariants"] + pkg_llm_replay["llm-replay"] + pkg_subagent_mock["subagent-mock"] + end + subgraph group_ui["packages/ui"] + pkg_acp["acp"] + pkg_acp_agent["acp-agent"] + pkg_stdio_agent["stdio-agent"] + end + pkg_llm --> pkg_brand + pkg_bash --> pkg_brand + pkg_llm_deepseek --> pkg_llm + pkg_llm_pi_ai --> pkg_llm + pkg_session --> pkg_brand + pkg_session --> pkg_llm + pkg_system_prompt --> pkg_llm + pkg_bash_local --> pkg_bash + pkg_fs --> pkg_brand + pkg_fs --> pkg_llm + pkg_web --> pkg_llm + pkg_agent --> pkg_brand + pkg_agent --> pkg_llm + pkg_agent --> pkg_session + pkg_fs_local --> pkg_fs + pkg_fs_policy --> pkg_fs + pkg_compact --> pkg_llm + pkg_compact --> pkg_session + pkg_web_fetch_local --> pkg_web + pkg_web_search_deepseek --> pkg_web + pkg_web_search_exa --> pkg_web + pkg_web_search_perplexity --> pkg_web + pkg_hook_protocol --> pkg_bash + pkg_hook_protocol --> pkg_session + pkg_session_persistence --> pkg_session + pkg_llm_replay --> pkg_llm + pkg_llm_replay --> pkg_session + pkg_tools --> pkg_agent + pkg_tools --> pkg_llm + pkg_tools --> pkg_system_prompt + pkg_compact_basic --> pkg_agent + pkg_compact_basic --> pkg_compact + pkg_compact_basic --> pkg_llm + pkg_compact_basic --> pkg_session + pkg_session_persistence_jsonl --> pkg_session + pkg_session_persistence_jsonl --> pkg_session_persistence + pkg_session_persistence_sqlite --> pkg_session + pkg_session_persistence_sqlite --> pkg_session_persistence + pkg_invariants --> pkg_agent + pkg_invariants --> pkg_llm + pkg_invariants --> pkg_session + pkg_agent_loop --> pkg_agent + pkg_agent_loop --> pkg_llm + pkg_agent_loop --> pkg_session + pkg_agent_loop --> pkg_session_persistence + pkg_agent_loop --> pkg_system_prompt + pkg_agent_loop --> pkg_tools + pkg_tool_bash --> pkg_agent + pkg_tool_bash --> pkg_bash + pkg_tool_bash --> pkg_llm + pkg_tool_bash --> pkg_tools + pkg_tool_fs --> pkg_fs + pkg_tool_fs --> pkg_llm + pkg_tool_fs --> pkg_session + pkg_tool_fs --> pkg_system_prompt + pkg_tool_fs --> pkg_tools + pkg_subagent --> pkg_agent + pkg_subagent --> pkg_llm + pkg_subagent --> pkg_tools + pkg_tool_web --> pkg_llm + pkg_tool_web --> pkg_system_prompt + pkg_tool_web --> pkg_tools + pkg_tool_web --> pkg_web + pkg_tool_todo --> pkg_agent + pkg_tool_todo --> pkg_session + pkg_tool_todo --> pkg_tools + pkg_hooks_codex --> pkg_agent + pkg_hooks_codex --> pkg_hook_protocol + pkg_hooks_codex --> pkg_llm + pkg_hooks_codex --> pkg_session + pkg_hooks_codex --> pkg_tools + pkg_acp --> pkg_agent + pkg_acp --> pkg_llm + pkg_acp --> pkg_session + pkg_acp --> pkg_session_persistence + pkg_acp --> pkg_tools + pkg_agent_core --> pkg_agent + pkg_agent_core --> pkg_agent_loop + pkg_agent_core --> pkg_invariants + pkg_agent_core --> pkg_llm + pkg_agent_core --> pkg_session + pkg_agent_core --> pkg_system_prompt + pkg_agent_core --> pkg_tool_bash + pkg_agent_core --> pkg_tools + pkg_subagent_acp --> pkg_agent + pkg_subagent_acp --> pkg_llm + pkg_subagent_acp --> pkg_subagent + pkg_subagent_inprocess --> pkg_agent + pkg_subagent_inprocess --> pkg_llm + pkg_subagent_inprocess --> pkg_session + pkg_subagent_inprocess --> pkg_subagent + pkg_tool_subagent --> pkg_agent + pkg_tool_subagent --> pkg_llm + pkg_tool_subagent --> pkg_subagent + pkg_tool_subagent --> pkg_tools + pkg_hooks_claude --> pkg_agent + pkg_hooks_claude --> pkg_hook_protocol + pkg_hooks_claude --> pkg_llm + pkg_hooks_claude --> pkg_session + pkg_hooks_claude --> pkg_subagent + pkg_hooks_claude --> pkg_tools + pkg_subagent_mock --> pkg_agent + pkg_subagent_mock --> pkg_llm + pkg_subagent_mock --> pkg_subagent + pkg_subagent_fork --> pkg_agent + pkg_subagent_fork --> pkg_session + pkg_subagent_fork --> pkg_subagent + pkg_subagent_fork --> pkg_subagent_inprocess + pkg_subagent_spawn --> pkg_subagent + pkg_subagent_spawn --> pkg_subagent_inprocess + pkg_acp_agent --> pkg_acp + pkg_acp_agent --> pkg_agent_core + pkg_acp_agent --> pkg_session_persistence_jsonl + pkg_stdio_agent --> pkg_agent + pkg_stdio_agent --> pkg_agent_core + pkg_stdio_agent --> pkg_llm + pkg_stdio_agent --> pkg_session + pkg_stdio_agent --> pkg_session_persistence_jsonl ``` -| Package | Depends on | -| --- | --- | -| `brand` | — | -| `bash` | `brand` | -| `llm` | `brand` | -| `bash-local` | `bash` | -| `fs` | `brand`, `llm` | -| `llm-deepseek` | `llm` | -| `llm-pi-ai` | `llm` | -| `session` | `brand`, `llm` | -| `system-prompt` | `llm` | -| `web` | `llm` | -| `agent` | `brand`, `llm`, `session` | -| `compact` | `llm`, `session` | -| `fs-local` | `fs` | -| `fs-policy` | `fs` | -| `hook-protocol` | `bash`, `session` | -| `llm-replay` | `llm`, `session` | -| `session-persistence` | `session` | -| `web-fetch-local` | `web` | -| `web-search-deepseek` | `web` | -| `web-search-exa` | `web` | -| `web-search-perplexity` | `web` | -| `compact-basic` | `agent`, `compact`, `llm`, `session` | -| `invariants` | `agent`, `llm`, `session` | -| `session-persistence-jsonl` | `session`, `session-persistence` | -| `session-persistence-sqlite` | `session`, `session-persistence` | -| `tools` | `agent`, `llm`, `system-prompt` | -| `acp` | `agent`, `llm`, `session`, `session-persistence`, `tools` | -| `agent-loop` | `agent`, `llm`, `session`, `session-persistence`, `system-prompt`, `tools` | -| `hooks-codex` | `agent`, `hook-protocol`, `llm`, `session`, `tools` | -| `subagent` | `agent`, `llm`, `tools` | -| `tool-bash` | `agent`, `bash`, `llm`, `tools` | -| `tool-fs` | `fs`, `llm`, `session`, `system-prompt`, `tools` | -| `tool-todo` | `agent`, `session`, `tools` | -| `tool-web` | `llm`, `system-prompt`, `tools`, `web` | -| `agent-core` | `agent`, `agent-loop`, `invariants`, `llm`, `session`, `system-prompt`, `tool-bash`, `tools` | -| `hooks-claude` | `agent`, `hook-protocol`, `llm`, `session`, `subagent`, `tools` | -| `subagent-acp` | `agent`, `llm`, `subagent` | -| `subagent-inprocess` | `agent`, `llm`, `session`, `subagent` | -| `subagent-mock` | `agent`, `llm`, `subagent` | -| `tool-subagent` | `agent`, `llm`, `subagent`, `tools` | -| `acp-agent` | `acp`, `agent-core`, `session-persistence-jsonl` | -| `stdio-agent` | `agent`, `agent-core`, `llm`, `session`, `session-persistence-jsonl` | -| `subagent-fork` | `agent`, `session`, `subagent`, `subagent-inprocess` | -| `subagent-spawn` | `subagent`, `subagent-inprocess` | +| Package | Group | Depends on | +| --- | --- | --- | +| [`brand`](../packages/util/brand) | `util` | — | +| [`llm`](../packages/llm/llm) | `llm` | [`brand`](../packages/util/brand) | +| [`bash`](../packages/bash/bash) | `bash` | [`brand`](../packages/util/brand) | +| [`llm-deepseek`](../packages/llm/llm-deepseek) | `llm` | [`llm`](../packages/llm/llm) | +| [`llm-pi-ai`](../packages/llm/llm-pi-ai) | `llm` | [`llm`](../packages/llm/llm) | +| [`session`](../packages/core/session) | `core` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm) | +| [`system-prompt`](../packages/core/system-prompt) | `core` | [`llm`](../packages/llm/llm) | +| [`bash-local`](../packages/bash/bash-local) | `bash` | [`bash`](../packages/bash/bash) | +| [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm) | +| [`web`](../packages/web/web) | `web` | [`llm`](../packages/llm/llm) | +| [`agent`](../packages/core/agent) | `core` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | +| [`fs-local`](../packages/fs/fs-local) | `fs` | [`fs`](../packages/fs/fs) | +| [`fs-policy`](../packages/fs/fs-policy) | `fs` | [`fs`](../packages/fs/fs) | +| [`compact`](../packages/compact/compact) | `compact` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | +| [`web-fetch-local`](../packages/web/web-fetch-local) | `web` | [`web`](../packages/web/web) | +| [`web-search-deepseek`](../packages/web/web-search-deepseek) | `web` | [`web`](../packages/web/web) | +| [`web-search-exa`](../packages/web/web-search-exa) | `web` | [`web`](../packages/web/web) | +| [`web-search-perplexity`](../packages/web/web-search-perplexity) | `web` | [`web`](../packages/web/web) | +| [`hook-protocol`](../packages/hooks/hook-protocol) | `hooks` | [`bash`](../packages/bash/bash), [`session`](../packages/core/session) | +| [`session-persistence`](../packages/session-persistence/session-persistence) | `session-persistence` | [`session`](../packages/core/session) | +| [`llm-replay`](../packages/support/llm-replay) | `support` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | +| [`tools`](../packages/core/tools) | `core` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt) | +| [`compact-basic`](../packages/compact/compact-basic) | `compact` | [`agent`](../packages/core/agent), [`compact`](../packages/compact/compact), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | +| [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl) | `session-persistence` | [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence) | +| [`session-persistence-sqlite`](../packages/session-persistence/session-persistence-sqlite) | `session-persistence` | [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence) | +| [`invariants`](../packages/support/invariants) | `support` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | +| [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | +| [`tool-bash`](../packages/bash/tool-bash) | `bash` | [`agent`](../packages/core/agent), [`bash`](../packages/bash/bash), [`llm`](../packages/llm/llm), [`tools`](../packages/core/tools) | +| [`tool-fs`](../packages/fs/tool-fs) | `fs` | [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | +| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`tools`](../packages/core/tools) | +| [`tool-web`](../packages/web/tool-web) | `web` | [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`web`](../packages/web/web) | +| [`tool-todo`](../packages/todo/tool-todo) | `todo` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`tools`](../packages/core/tools) | +| [`hooks-codex`](../packages/hooks/hooks-codex) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`tools`](../packages/core/tools) | +| [`acp`](../packages/ui/acp) | `ui` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`tools`](../packages/core/tools) | +| [`agent-core`](../packages/core/agent-core) | `core` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tool-bash`](../packages/bash/tool-bash), [`tools`](../packages/core/tools) | +| [`subagent-acp`](../packages/subagent/subagent-acp) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent) | +| [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) | +| [`tool-subagent`](../packages/subagent/tool-subagent) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | +| [`hooks-claude`](../packages/hooks/hooks-claude) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | +| [`subagent-mock`](../packages/support/subagent-mock) | `support` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent) | +| [`subagent-fork`](../packages/subagent/subagent-fork) | `subagent` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | +| [`subagent-spawn`](../packages/subagent/subagent-spawn) | `subagent` | [`subagent`](../packages/subagent/subagent), [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | +| [`acp-agent`](../packages/ui/acp-agent) | `ui` | [`acp`](../packages/ui/acp), [`agent-core`](../packages/core/agent-core), [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl) | +| [`stdio-agent`](../packages/ui/stdio-agent) | `ui` | [`agent`](../packages/core/agent), [`agent-core`](../packages/core/agent-core), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl) | diff --git a/docs/rfc/README.md b/docs/rfc/README.md index d04d6a37cb..1b5a3d350e 100644 --- a/docs/rfc/README.md +++ b/docs/rfc/README.md @@ -169,7 +169,7 @@ Do NOT write one for a mechanical or local choice (a variable name, a one-file r | [Classify RFCs by kind via path-encoded subdirectories](implemented/process/2026-06-20-rfc-classification.md) | 2026-06-20 | | [Bilingual documentation via paired sibling files and a pairing gate](implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md) | 2026-07-02 | | [Generated tool-schema catalog (boot-and-harvest)](implemented/process/2026-07-02-tool-schema-catalog.md) | 2026-07-02 | -| [Documentation graph atlas for maintainers and SDK users](implemented/process/2026-07-03-documentation-graph-atlas.md) | 2026-07-03 | +| [Documentation graph index for maintainers and SDK users](implemented/process/2026-07-03-documentation-graph-atlas.md) | 2026-07-03 | | [JSDoc completeness gate for the cordis surface](implemented/process/2026-07-04-cordis-jsdoc-completeness-gate.md) | 2026-07-04 | | [Documentation tiers, budgets, and the ceiling gate](implemented/process/2026-07-04-doc-tiers-and-budgets.md) | 2026-07-04 | | [Generate the RFC index tables](implemented/process/2026-07-04-generate-rfc-index-tables.md) | 2026-07-04 | diff --git a/docs/rfc/implemented/process/2026-07-03-documentation-graph-atlas.md b/docs/rfc/implemented/process/2026-07-03-documentation-graph-atlas.md index 97c7b13bf6..d10aa6de61 100644 --- a/docs/rfc/implemented/process/2026-07-03-documentation-graph-atlas.md +++ b/docs/rfc/implemented/process/2026-07-03-documentation-graph-atlas.md @@ -1,4 +1,4 @@ -# RFC: Documentation graph atlas for maintainers and SDK users +# RFC: Documentation graph index for maintainers and SDK users Status: implemented (accepted 2026-07-03) @@ -12,9 +12,9 @@ The pressure is already visible in the open stacks even though this implementati ## Decision -Add a generated graph atlas under [docs/graphs/](../../../graphs/README.md), produced by `scripts/gen-doc-graphs.ts` and verified by `pnpm run verify-doc-graphs` as part of `doc-sync`. +Add generated relationship graph docs, indexed at [docs/graph-atlas.md](../../../graph-atlas.md), produced by focused generators and verified by `pnpm run verify-doc-graphs` / existing catalog freshness checks as part of `doc-sync`. -The atlas is a relationship layer above the existing catalogs. It does not replace exact references; instead, it links to them and explains how their pieces fit together. +The index is a relationship layer above the existing catalogs. It does not replace exact references; instead, it links to them and explains how their pieces fit together. ### Maintenance modes @@ -22,36 +22,36 @@ Every graph page declares one maintenance mode: - **Generated**: all nodes and edges are discovered from source; `--check` fails if the committed artifact is stale. - **Hybrid generated**: source discovers the inventory, a small manifest classifies irreducible policy, and a completeness guard fails if discovered items are unclassified. -- **Curated**: the diagram explains design intent, temporal order, or ownership; it is emitted by the generator so the atlas remains a single regenerated unit, but the content is deliberately authored. +- **Curated**: the diagram explains design intent, temporal order, or ownership; it is emitted by the generator so the graph docs remain a regenerated unit, but the content is deliberately authored. -### First shipped atlas +### First shipped index -The first atlas ships twelve files: the index plus eleven graph pages. +The first index links ten relationship surfaces. Package topology and tool-package affordances live in the existing generated catalogs that already own those facts; the remaining focused diagrams are generated by `scripts/gen-doc-graphs.ts`. | Graph | Maintenance mode | Source of truth | |---|---|---| -| [package topology by group](../../../graphs/package-topology.md) | generated | `packages/*/*/package.json` peer dependencies plus package group paths | -| [capability seams and core services](../../../graphs/capability-seams.md) | hybrid generated | Cordis service declarations plus a role manifest in `gen-doc-graphs.ts` | -| [app composition](../../../graphs/app-composition.md) | hybrid generated | `examples/*/cordis.yml` plugin lists plus curated app/bundle expansions | -| [event producer/consumer matrix](../../../graphs/event-producer-consumer.md) | hybrid generated | Cordis event declarations, AST-scanned `ctx.on/emit/parallel/serial/waterfall` sites, and explicit dynamic dispatch overrides | -| [tool affordance map](../../../graphs/tool-affordance-map.md) | hybrid generated | boot-harvested tool catalog plus a manifest of required services and shipped aliases | -| [agent turn and step lifecycle](../../../graphs/agent-lifecycle.md) | curated | architecture.md loop lifecycle, Cordis catalog links, and session event semantics | -| [tool execution pipeline](../../../graphs/tool-execution-pipeline.md) | curated | tool pipeline semantics and the `tools/execute` waterfall | -| [session surface and message projection](../../../graphs/session-surface.md) | curated | session surface/event-sourcing docs | -| [subagent and session lineage](../../../graphs/subagent-lineage.md) | curated | subagent seam docs and replay/fork semantics | -| [plugin disposal and hot reload ownership](../../../graphs/hot-reload-disposal.md) | curated | Cordis fiber/effect ownership conventions | -| [ACP snapshot replay](../../../graphs/snapshot-replay.md) | curated | snapshot harness behavior | +| [module dependency graph](../../../module-graph.md) | generated | `packages/*/*/package.json` peer dependencies plus package group paths | +| [tool schema catalog and package map](../../../tool-catalog/tools.md) | generated | boot-harvested tool schemas plus tool-package service/effect metadata | +| [capability seams and core services](../../../capability-seams.md) | hybrid generated | Cordis service declarations plus a role manifest in `gen-doc-graphs.ts` | +| [echo-agent app composition](../../../echo-agent-composition.md) | hybrid generated | `examples/echo-agent/cordis.yml` plugin list plus curated app/bundle expansion | +| [coding-agent app composition](../../../coding-agent-composition.md) | hybrid generated | `examples/coding-agent/cordis.yml` plugin list plus curated app/bundle expansion | +| [acp-agent app composition](../../../acp-agent-composition.md) | hybrid generated | `examples/acp-agent/cordis.yml` plugin list plus curated app/bundle expansion | +| [event producer/consumer matrix](../../../event-producer-consumer.md) | hybrid generated | Cordis event declarations, AST-scanned `ctx.on/emit/parallel/serial/waterfall` sites, and explicit dynamic dispatch overrides | +| [agent turn and step lifecycle](../../../agent-lifecycle.md) | curated | architecture.md loop lifecycle, Cordis catalog links, and session event semantics | +| [tool execution pipeline](../../../tool-execution-pipeline.md) | curated | tool pipeline semantics and the `tools/execute` waterfall | +| [ACP snapshot replay](../../../acp/snapshot-replay.md) | curated | snapshot harness behavior | -### Why one generator +### Why generators own the docs -Keeping the atlas behind one generator gives reviewers one freshness gate and keeps cross-page terminology synchronized. The tradeoff is that curated diagrams are edited in TypeScript string blocks rather than directly in Markdown. That is acceptable for this first cut because the user-facing artifact is still plain Markdown/Mermaid, and a future change can split the curated pages out if authorship ergonomics matter more than one-command regeneration. +Package topology stays in `gen-module-graph.ts`, and tool-package affordances stay in `gen-tool-catalog.ts`, because those generators already own the canonical facts and freshness gates. `gen-doc-graphs.ts` owns the remaining relationship pages and the index. The tradeoff is that curated diagrams are edited in TypeScript string blocks rather than directly in Markdown. That is acceptable for this first cut because the user-facing artifact is still plain Markdown/Mermaid, and a future change can split the curated pages out if authorship ergonomics matter more than regeneration. ### Completeness guards The hybrid pages must fail loud when their manifests are stale: +- The module graph reads every package's `peerDependencies` and groups each package by its `packages//` path. +- The tool catalog boot-harvests shipped tools and renders the package/service/effect map from the same manifest that its completeness guard already checks. - The capability seam graph imports the Cordis service collector and asserts every discovered harness `ctx.` is classified in `SERVICE_ROLES`, and every classified key still exists. -- The tool affordance graph boot-harvests the shipped tool catalog and asserts every tool package has `TOOL_PACKAGE_META`. - The event producer/consumer matrix labels itself hybrid because subagent lifecycle events deliberately use `ctx.events.dispatch` for per-listener containment; those dynamic edges are explicit overrides rather than invisible omissions. - `verify-mermaid` parses every repo-authored ` ```mermaid ` fence with Mermaid's own parser, so syntax errors fail `doc-sync` locally and in CI instead of showing up as broken GitHub-rendered diagrams. @@ -61,7 +61,7 @@ Use Mermaid for committed diagrams because GitHub renders it in Markdown and it ## Consequences -- Maintainers get visual entry points for topology, seams, event flow, lifecycle, session replay, and snapshot behavior. +- Maintainers get visual entry points for topology, seams, event flow, lifecycle, app composition, and snapshot behavior. - SDK users get a path from use case to package composition instead of only bottom-up package references. - `doc-sync` now includes `verify-doc-graphs` and `verify-mermaid`, so graph drift and Mermaid syntax errors are caught with the other doc freshness gates. -- Future fs and hooks work has a concrete place to land new complexity: fs should expand the capability and tool graphs, while hooks should expand the event matrix and tool execution pipeline. +- Future fs and hooks work has a concrete place to land new complexity: fs should expand the capability docs and tool catalog, while hooks should expand the event matrix and tool execution pipeline. diff --git a/docs/tool-catalog/tools.md b/docs/tool-catalog/tools.md index 5b81b9d9d7..b6eb25c447 100644 --- a/docs/tool-catalog/tools.md +++ b/docs/tool-catalog/tools.md @@ -9,6 +9,18 @@ This file is GENERATED and verified fresh by `pnpm run verify-tool-catalog` (par Scope: shipped product tools under `packages/*/tool-*`, each booted with its DEFAULT config. The registered tool NAME can be a load-time config (e.g. `tool-subagent`'s `toolName`), so a deployment may surface a package under a different or additional name — a per-package note records those shipped aliases where they exist. The `examples/` demo tools (e.g. `echo`) are excluded, matching the cordis catalog's packages-only scope. +## Tool Package Map + +This table connects model-visible tool names to the plugin package and service seams behind them. Exact JSON Schemas follow in the package sections below. + +| Tool package | Model-visible names | Requires | Writes / affects | Shipped aliases | Deployment note | +| --- | --- | --- | --- | --- | --- | +| `@deepseek-ai/dsh-tool-bash` | `bash`, `bash_kill`, `bash_output` | `ctx.tools`, `ctx.bash` | `tool/call`, `tool/result`, `context/message via agent.inject() for background completion notices` | - | The bash/bash_output/bash_kill tools are model-facing consumers of the bash executor seam. | +| `@deepseek-ai/dsh-tool-fs` | `edit`, `read`, `write` | `ctx.tools`, `ctx.fs`, `ctx.systemPrompt` | `tool/call`, `fs/write-intent or fs/edit-intent for mutations`, `fs/observed after successful file operations`, `tool/result` | - | The read-before-write/edit policy is added by `@deepseek-ai/dsh-fs-policy` (an `fs/*` event-gate plugin, no schema change); a deployment that loads these tools is expected to also load it. The tool schemas above are identical with or without the policy plugin. | +| `@deepseek-ai/dsh-tool-subagent` | `subagent` | `ctx.tools`, `ctx.subagents` | `tool/call`, `tool/result`, `child session events through the chosen provider` | `subagent`, `subagent_fork` | The registered tool name is the load-time `toolName` config (default `subagent`); the schema above is that default. The shipped example agents load this package once per subagent backend, so the model additionally sees `subagent_fork` (bound to the fork backend) with an identical schema — see `examples/coding-agent/cordis.yml` and `examples/acp-agent/cordis.yml`. | +| `@deepseek-ai/dsh-tool-todo` | `todo_write` | `ctx.tools`, `owning Agent session` | `tool/call`, `todo/write`, `tool/result` | - | todo_write is session-owned state; UIs render the latest todo/write event as a checklist or ACP plan. | +| `@deepseek-ai/dsh-tool-web` | `web_fetch`, `web_search` | `ctx.tools`, `ctx.web`, `ctx.systemPrompt` | `tool/call`, `tool/result` | - | web_search and web_fetch keep provider selection behind ctx.web so model-visible schemas stay stable across backend swaps. | + ## `@deepseek-ai/dsh-tool-bash` ### `bash` @@ -91,6 +103,8 @@ Read new output from a background bash task started with `bash` + `run_in_backgr Source: [`packages/bash/tool-bash/src/index.ts`](../../packages/bash/tool-bash/src/index.ts) +The bash/bash_output/bash_kill tools are model-facing consumers of the bash executor seam. + ## `@deepseek-ai/dsh-tool-fs` ### `edit` @@ -260,6 +274,8 @@ Record and update a structured task list for the current work. Send the ENTIRE l Source: [`packages/todo/tool-todo/src/index.ts`](../../packages/todo/tool-todo/src/index.ts) +todo_write is session-owned state; UIs render the latest todo/write event as a checklist or ACP plan. + ## `@deepseek-ai/dsh-tool-web` ### `web_fetch` @@ -307,3 +323,5 @@ Search the web for current information. Returns an optional summary answer and a ``` Source: [`packages/web/tool-web/src/index.ts`](../../packages/web/tool-web/src/index.ts) + +web_search and web_fetch keep provider selection behind ctx.web so model-visible schemas stay stable across backend swaps. diff --git a/docs/graphs/tool-execution-pipeline.md b/docs/tool-execution-pipeline.md similarity index 71% rename from docs/graphs/tool-execution-pipeline.md rename to docs/tool-execution-pipeline.md index a5f0aeb36f..81bac5eaaf 100644 --- a/docs/graphs/tool-execution-pipeline.md +++ b/docs/tool-execution-pipeline.md @@ -10,16 +10,16 @@ This graph shows where policy, hooks, sandboxing, filesystem guards, result rewr ```mermaid flowchart TD model["Assistant message contains tool-call block"] - toolCall["Session event: tool/call
logged before execution"] + toolCall["Session event: tool/call
logged before execution"] presentCall["UI pending card
presentCall(args)"] - pre["tools/pre-execute waterfall
hooks, permission, sandbox"] + pre["tools/pre-execute waterfall
hooks, permission, sandbox"] denied["deny or ask
tool body skipped"] toolBody["Registered tool execute() body"] - fsGate["fs/write-intent or fs/edit-intent
tool-fs mutations only"] - owned["Tool-owned session events
todo/write, fs/observed, hook/invoked, hook/result"] - post["tools/post-execute waterfall
accept, block, replace, add context"] + fsGate["fs/write-intent or fs/edit-intent
tool-fs mutations only"] + owned["Tool-owned session events
todo/write, fs/observed, hook/invoked, hook/result"] + post["tools/post-execute waterfall
accept, block, replace, add context"] context["Buffered additionalContext
context/message after all tool results"] - toolResult["Session event: tool/result
single model-facing outcome"] + toolResult["Session event: tool/result
single model-facing outcome"] presentResult["UI completed card
presentResult(args, result)"] model --> toolCall toolCall --> presentCall diff --git a/packages/core/tools/tests/gen-tool-catalog.spec.ts b/packages/core/tools/tests/gen-tool-catalog.spec.ts index f034c0529f..efc9ae44e5 100644 --- a/packages/core/tools/tests/gen-tool-catalog.spec.ts +++ b/packages/core/tools/tests/gen-tool-catalog.spec.ts @@ -93,10 +93,13 @@ describe('gen-tool-catalog render', () => { { pkg: '@deepseek-ai/dsh-tool-demo', source: 'packages/demo/tool-demo/src/index.ts', + requires: ['ctx.tools'], + writes: ['tool/result'], schemas: [{ name: 'demo', description: 'A demo tool.', parameters: { type: 'object', properties: {} } }], }, ] const md = render(catalog) + expect(md).toContain('| `@deepseek-ai/dsh-tool-demo` | `demo` | `ctx.tools` | `tool/result` |') expect(md).toContain('## `@deepseek-ai/dsh-tool-demo`') expect(md).toContain('### `demo`') expect(md).toContain('A demo tool.') @@ -109,6 +112,8 @@ describe('gen-tool-catalog render', () => { { pkg: '@deepseek-ai/dsh-tool-demo', source: 'packages/demo/tool-demo/src/index.ts', + requires: ['ctx.tools'], + writes: ['tool/result'], schemas: [{ name: 'demo', description: '', parameters: { type: 'object', properties: {} }, strict: true }], }, ] diff --git a/scripts/gen-doc-graphs.ts b/scripts/gen-doc-graphs.ts index 07c09427de..92784fd416 100644 --- a/scripts/gen-doc-graphs.ts +++ b/scripts/gen-doc-graphs.ts @@ -1,20 +1,20 @@ /** - * Generate (and verify) the documentation graph atlas in docs/graphs/. + * Generate (and verify) the relationship-diagram docs. * * This is the relationship layer above the existing catalogs: * - module-graph.md answers "which packages depend on which packages?" * - cordis-catalog/ answers "which events and services exist?" * - tool-catalog/ answers "which tools does the model see?" - * - docs/graphs/ answers "how do those pieces fit together?" + * - docs/*.md relationship diagrams answer "how do those pieces fit together?" * * Generated pages discover the enumerable facts from source. Hybrid pages use * discovered inventory plus small manifests for policy that source cannot infer * (for example, whether a package is an implementation or consumer in a seam). - * Curated pages are still emitted here so the atlas is one regenerated unit, + * Curated pages are still emitted here so the graph docs are one regenerated unit, * but their diagrams intentionally explain flow and ownership rather than * pretending to enumerate every source edge. * - * `tsx scripts/gen-doc-graphs.ts` -> write docs/graphs/*.md + * `tsx scripts/gen-doc-graphs.ts` -> write generated diagram docs * `tsx scripts/gen-doc-graphs.ts --check` -> exit 1 if any file is stale */ @@ -22,10 +22,8 @@ import { existsSync, globSync, mkdirSync, readFileSync, writeFileSync } from 'no import { dirname, resolve } from 'node:path' import ts from 'typescript' import { collectEvents, collectServices } from './gen-cordis-catalog.ts' -import { collectToolCatalog } from './gen-tool-catalog.ts' const root = resolve(import.meta.dirname, '..') -const OUT_DIR = 'docs/graphs' const SCOPE = '@deepseek-ai/dsh-' interface PkgJson { @@ -67,13 +65,6 @@ interface EventRelation { listeners: Set } -interface ToolPackageMeta { - requires: string[] - writes: string[] - shippedNames?: string[] - note: string -} - const GROUP_ORDER = [ 'util', 'llm', @@ -197,35 +188,6 @@ const SERVICE_ROLES: ServiceRole[] = [ }, ] -const TOOL_PACKAGE_META: Record = { - '@deepseek-ai/dsh-tool-bash': { - requires: ['ctx.tools', 'ctx.bash'], - writes: ['tool/call', 'tool/result', 'context/message via agent.inject() for background completion notices'], - note: 'The bash/bash_output/bash_kill tools are model-facing consumers of the bash executor seam.', - }, - '@deepseek-ai/dsh-tool-fs': { - requires: ['ctx.tools', 'ctx.fs', 'ctx.systemPrompt'], - writes: ['tool/call', 'fs/write-intent or fs/edit-intent for mutations', 'fs/observed after successful file operations', 'tool/result'], - note: 'read/write/edit are the model-facing filesystem tools; read windowing lives here, while read-before-edit policy is supplied by fs-policy through fs/* events.', - }, - '@deepseek-ai/dsh-tool-subagent': { - requires: ['ctx.tools', 'ctx.subagents'], - writes: ['tool/call', 'tool/result', 'child session events through the chosen provider'], - shippedNames: ['subagent', 'subagent_fork'], - note: 'The default package schema registers subagent; shipped coding/acp configs load it twice to expose spawn and fork backends.', - }, - '@deepseek-ai/dsh-tool-todo': { - requires: ['ctx.tools', 'owning Agent session'], - writes: ['tool/call', 'todo/write', 'tool/result'], - note: 'todo_write is session-owned state; UIs render the latest todo/write event as a checklist or ACP plan.', - }, - '@deepseek-ai/dsh-tool-web': { - requires: ['ctx.tools', 'ctx.web', 'ctx.systemPrompt'], - writes: ['tool/call', 'tool/result'], - note: 'web_search and web_fetch keep provider selection behind ctx.web so model-visible schemas stay stable across backend swaps.', - }, -} - const DYNAMIC_EVENT_DISPATCHERS: Array<{ event: string; pkg: string; method: string }> = [ // Subagent lifecycle events intentionally bypass ctx.emit and call // ctx.events.dispatch directly so one throwing listener cannot starve later @@ -302,8 +264,20 @@ function escLabel(value: string): string { return value.replace(/"/g, '\\"') } -function pkgLink(pkg: Pkg | undefined, fallback: string): string { - return pkg ? `[\`${pkg.short}\`](../../${pkg.rel})` : `\`${fallback}\`` +function mermaidCode(value: string): string { + return `${value.replace(/&/g, '&').replace(//g, '>')}` +} + +function repoLink(path: string, label: string, up = '..'): string { + return `[${label}](${up}/${path})` +} + +function sourceLink(source: string, up = '..'): string { + return repoLink(source.split(':')[0] ?? source, `\`${source}\``, up) +} + +function pkgLink(pkg: Pkg | undefined, fallback: string, up = '..'): string { + return pkg ? repoLink(pkg.rel, `\`${pkg.short}\``, up) : `\`${fallback}\`` } function pkgList(names: string[] | undefined, pkgsByShort: Map): string { @@ -311,52 +285,10 @@ function pkgList(names: string[] | undefined, pkgsByShort: Map): st return names.map(name => pkgLink(pkgsByShort.get(name), name)).join(', ') } -function codeList(values: string[]): string { - return values.length ? values.map(v => `\`${v}\``).join(', ') : '-' -} - function tableCell(value: string): string { return value.replace(/\|/g, '\\|').replace(/\n/g, '
') } -function renderMermaidPackageNode(pkg: Pkg): string { - return ` ${nodeId('pkg', pkg.short)}["${escLabel(pkg.short)}"]` -} - -function renderPackageTopology(pkgs: Pkg[]): string { - const lines = generatedHeader('Package Topology By Group', 'generated from `packages/*/*/package.json` peer dependencies plus package group paths') - lines.push( - 'This graph complements [module-graph.md](../module-graph.md): it keeps the same canonical peer-dependency edge source, but clusters packages by the `packages//` hierarchy so layering and capability families are easier to scan.', - '', - '```mermaid', - 'flowchart TD', - ) - const groups = [...new Set(pkgs.map(pkg => pkg.group))].sort((a, b) => { - const ia = GROUP_ORDER.indexOf(a) - const ib = GROUP_ORDER.indexOf(b) - const na = ia === -1 ? Number.MAX_SAFE_INTEGER : ia - const nb = ib === -1 ? Number.MAX_SAFE_INTEGER : ib - return na - nb || a.localeCompare(b) - }) - for (const group of groups) { - lines.push(` subgraph ${nodeId('group', group)}["packages/${escLabel(group)}"]`) - for (const pkg of pkgs.filter(p => p.group === group).sort((a, b) => a.short.localeCompare(b.short))) { - lines.push(renderMermaidPackageNode(pkg)) - } - lines.push(' end') - } - for (const pkg of pkgs) { - for (const dep of pkg.deps) lines.push(` ${nodeId('pkg', pkg.short)} --> ${nodeId('pkg', dep)}`) - } - lines.push('```', '', '| Package | Group | Depends on |', '| --- | --- | --- |') - const byShort = new Map(pkgs.map(pkg => [pkg.short, pkg])) - for (const pkg of pkgs) { - lines.push(`| ${pkgLink(pkg, pkg.short)} | \`${pkg.group}\` | ${pkg.deps.length ? pkg.deps.map(dep => pkgLink(byShort.get(dep), dep)).join(', ') : '-'} |`) - } - lines.push('') - return lines.join('\n') -} - function assertServiceRolesComplete(): void { const discovered = new Set(collectServices().map(service => service.key)) const classified = new Set(SERVICE_ROLES.map(role => role.key)) @@ -440,56 +372,81 @@ function stripYamlScalar(value: string): string { return value.trim().replace(/^['"]|['"]$/g, '') } -function renderAppComposition(): string { - const examples = [ - { id: 'echo', label: 'examples/echo-agent', config: 'examples/echo-agent/cordis.yml' }, - { id: 'coding', label: 'examples/coding-agent', config: 'examples/coding-agent/cordis.yml' }, - { id: 'acp', label: 'examples/acp-agent', config: 'examples/acp-agent/cordis.yml' }, - ] - const lines = generatedHeader('App Composition', 'hybrid: leaf plugin lists are parsed from `examples/*/cordis.yml`; bundle expansions are curated from app package source') +const APP_EXAMPLES = [ + { + id: 'echo', + rel: 'docs/echo-agent-composition.md', + title: 'Echo Agent App Composition', + label: 'examples/echo-agent', + config: 'examples/echo-agent/cordis.yml', + summary: 'The echo demo swaps in a local mock LLM and teaching echo tool, then loads the stdio app package for the shared spine and terminal front door.', + }, + { + id: 'coding', + rel: 'docs/coding-agent-composition.md', + title: 'Coding Agent App Composition', + label: 'examples/coding-agent', + config: 'examples/coding-agent/cordis.yml', + summary: 'The coding REPL demo adds the real DeepSeek adapter, filesystem tools, todo_write, compaction, and both subagent transports on top of the stdio app package.', + }, + { + id: 'acp', + rel: 'docs/acp-agent-composition.md', + title: 'ACP Agent App Composition', + label: 'examples/acp-agent', + config: 'examples/acp-agent/cordis.yml', + summary: 'The ACP demo exposes the same agent spine over JSON-RPC stdio, with no stdout logger and no pre-created agent; clients create sessions through the ACP bridge.', + }, +] + +type AppExample = typeof APP_EXAMPLES[number] + +function renderAppExpansion(lines: string[], appNode: string, pluginName: string): void { + const agentCore = nodeId('bundle', 'agent_core') + const jsonl = nodeId('bundle', 'jsonl') + lines.push(` ${appNode} --> ${agentCore}["@deepseek-ai/dsh-agent-core"]`) + lines.push(` ${appNode} --> ${jsonl}["@deepseek-ai/dsh-session-persistence-jsonl"]`) + if (pluginName === '@deepseek-ai/dsh-stdio-agent') { + lines.push(` ${appNode} --> ${nodeId('frontdoor', 'stdio')}["readline UI
console logger
pre-created main agent"]`) + } else if (pluginName === '@deepseek-ai/dsh-acp-agent') { + lines.push(` ${appNode} --> ${nodeId('frontdoor', 'acp')}["@deepseek-ai/dsh-acp
JSON-RPC stdio bridge
sessions created by client"]`) + } lines.push( - 'This graph is for SDK users asking which pieces a runnable agent loads. Leaf configs choose adapters and optional product tools; app packages provide the front door; `dsh-agent-core` bundles the providerless spine.', + ` ${agentCore} --> ${nodeId('spine', 'llm')}["ctx.llm"]`, + ` ${agentCore} --> ${nodeId('spine', 'sessions')}["ctx.sessions"]`, + ` ${agentCore} --> ${nodeId('spine', 'tools')}["ctx.tools + tool-bash"]`, + ` ${agentCore} --> ${nodeId('spine', 'loop')}["ctx.agents + ctx.agentLoop"]`, + ) +} + +function renderAppComposition(example: AppExample): string { + const plugins = parseExampleCordis(example.config) + const lines = generatedHeader(example.title, 'hybrid: the leaf plugin list is parsed from its `cordis.yml`; app package expansion is curated from package source') + lines.push( + example.summary, '', '```mermaid', 'flowchart LR', + ` cfg["${escLabel(example.label)}
cordis.yml"]`, ) - const bundleTargets: Record = { - '@deepseek-ai/dsh-stdio-agent': nodeId('bundle', 'stdio'), - '@deepseek-ai/dsh-acp-agent': nodeId('bundle', 'acp_agent'), - } - for (const example of examples) { - lines.push(` subgraph ${nodeId('example', example.id)}["${escLabel(example.label)}"]`) - lines.push(` ${nodeId('cfg', example.id)}["cordis.yml"]`) - for (const plugin of parseExampleCordis(example.config)) { - const pluginNode = nodeId(`plugin_${example.id}`, plugin.id) - lines.push(` ${pluginNode}["${escLabel(plugin.id)}
${escLabel(plugin.name)}"]`) - lines.push(` ${nodeId('cfg', example.id)} --> ${pluginNode}`) - const bundle = bundleTargets[plugin.name] - if (bundle !== undefined) lines.push(` ${pluginNode} --> ${bundle}`) + for (const plugin of plugins) { + const pluginNode = nodeId(`plugin_${example.id}`, plugin.id) + lines.push(` ${pluginNode}["${escLabel(plugin.id)}
${escLabel(plugin.name)}"]`) + lines.push(` cfg --> ${pluginNode}`) + if (plugin.name === '@deepseek-ai/dsh-stdio-agent' || plugin.name === '@deepseek-ai/dsh-acp-agent') { + renderAppExpansion(lines, pluginNode, plugin.name) } - lines.push(' end') } lines.push( - ` ${nodeId('bundle', 'stdio')}["@deepseek-ai/dsh-stdio-agent"] --> ${nodeId('bundle', 'agent_core')}["@deepseek-ai/dsh-agent-core"]`, - ` ${nodeId('bundle', 'stdio')} --> ${nodeId('bundle', 'jsonl')}["@deepseek-ai/dsh-session-persistence-jsonl"]`, - ` ${nodeId('bundle', 'stdio')} --> ${nodeId('bundle', 'ui_stdio')}["@deepseek-ai/dsh-ui-stdio"]`, - ` ${nodeId('bundle', 'acp_agent')}["@deepseek-ai/dsh-acp-agent"] --> ${nodeId('bundle', 'agent_core')}`, - ` ${nodeId('bundle', 'acp_agent')} --> ${nodeId('bundle', 'jsonl')}`, - ` ${nodeId('bundle', 'acp_agent')} --> ${nodeId('bundle', 'acp')}["@deepseek-ai/dsh-acp"]`, - ` ${nodeId('bundle', 'agent_core')} --> ${nodeId('spine', 'llm')}["ctx.llm"]`, - ` ${nodeId('bundle', 'agent_core')} --> ${nodeId('spine', 'sessions')}["ctx.sessions"]`, - ` ${nodeId('bundle', 'agent_core')} --> ${nodeId('spine', 'tools')}["ctx.tools + tool-bash"]`, - ` ${nodeId('bundle', 'agent_core')} --> ${nodeId('spine', 'loop')}["ctx.agents + ctx.agentLoop"]`, '```', '', - '| Example | Parsed plugin ids | Config |', - '| --- | --- | --- |', + '| Plugin id | Package / module |', + '| --- | --- |', + ...plugins.map(plugin => `| \`${plugin.id}\` | \`${plugin.name}\` |`), + '', + `Source config: ${repoLink(example.config, `\`${example.config}\``, '..')}.`, + '', ) - for (const example of examples) { - const plugins = parseExampleCordis(example.config) - lines.push(`| \`${example.label}\` | ${plugins.map(plugin => `\`${plugin.id}\``).join(', ')} | [\`${example.config}\`](../../${example.config}) |`) - } - lines.push('') return lines.join('\n') } @@ -580,7 +537,7 @@ function renderEventRelations(pkgs: Pkg[]): string { ) for (const event of [...events].sort((a, b) => a.name.localeCompare(b.name))) { const relation = relations.get(event.name) ?? { dispatchers: new Map>(), listeners: new Set() } - lines.push(`| \`${event.name}\` | \`${event.mode}\` | [\`${event.source}\`](../../${event.source.split(':')[0]}) | ${relationPackages(relation.dispatchers, pkgsByShort)} | ${listenerPackages(relation.listeners, pkgsByShort)} |`) + lines.push(`| \`${event.name}\` | \`${event.mode}\` | ${sourceLink(event.source)} | ${relationPackages(relation.dispatchers, pkgsByShort)} | ${listenerPackages(relation.listeners, pkgsByShort)} |`) } const declared = new Set(events.map(event => event.name)) const extra = [...relations.keys()].filter(event => !declared.has(event)).sort() @@ -596,52 +553,10 @@ function renderEventRelations(pkgs: Pkg[]): string { return lines.join('\n') } -async function renderToolAffordance(): Promise { - const catalog = await collectToolCatalog() - const lines = generatedHeader('Tool Affordance Map', 'hybrid: tool names/schemas are boot-harvested from shipped tool plugins; required services and shipped aliases are classified in `scripts/gen-doc-graphs.ts` with a completeness guard') - for (const entry of catalog) { - if (!TOOL_PACKAGE_META[entry.pkg]) { - throw new Error(`gen-doc-graphs: tool package ${entry.pkg} is missing TOOL_PACKAGE_META classification`) - } - } - lines.push( - 'This page connects the model-visible tools to the plugin packages and service seams behind them. For exact JSON Schemas, see [tool-catalog/tools.md](../tool-catalog/tools.md).', - '', - '```mermaid', - 'flowchart LR', - ' model["Model request tools[]"]', - ) - const requirementNodes = new Set() - for (const entry of catalog) { - const meta = TOOL_PACKAGE_META[entry.pkg] - if (!meta) continue - const packageNode = nodeId('toolpkg', entry.pkg) - const names = entry.schemas.map(schema => schema.name).join(', ') - lines.push(` ${packageNode}["${escLabel(entry.pkg.replace(SCOPE, ''))}
${escLabel(names)}"]`) - lines.push(` model --> ${packageNode}`) - for (const req of meta.requires) { - const reqNode = nodeId('requires', req) - if (!requirementNodes.has(reqNode)) { - lines.push(` ${reqNode}["${escLabel(req)}"]`) - requirementNodes.add(reqNode) - } - lines.push(` ${packageNode} --> ${reqNode}`) - } - } - lines.push('```', '', '| Tool package | Model-visible names | Requires | Writes / affects | Shipped aliases | Note |', '| --- | --- | --- | --- | --- | --- |') - for (const entry of catalog) { - const meta = TOOL_PACKAGE_META[entry.pkg] - if (!meta) continue - lines.push(`| \`${entry.pkg}\` | ${codeList(entry.schemas.map(schema => schema.name))} | ${codeList(meta.requires)} | ${codeList(meta.writes)} | ${codeList(meta.shippedNames ?? [])} | ${tableCell(meta.note)} |`) - } - lines.push('') - return lines.join('\n') -} - function renderLifecycle(): string { return [ ...generatedHeader('Agent Turn And Step Lifecycle', 'curated Mermaid sequence; exact event signatures live in the generated Cordis catalog'), - 'This sequence is the visual companion to [architecture.md](../architecture.md#loop-lifecycle-session--turn--step). It keeps durable replay facts on `session/event` and live control/status on `agent/*`.', + 'This sequence is the visual companion to [architecture.md](architecture.md#loop-lifecycle-session--turn--step). It keeps durable replay facts on `session/event` and live control/status on `agent/*`.', '', '```mermaid', 'sequenceDiagram', @@ -656,30 +571,30 @@ function renderLifecycle(): string { ' participant Persistence', ' participant SDK as UI or SDK listener', ' User->>Agent: send(content)', - ' Agent-->>SDK: agent/queued', + ` Agent-->>SDK: ${mermaidCode('agent/queued')}`, ' Agent->>Driver: queued work wakes driver', - ' Driver-->>SDK: agent/status running', - ' Driver->>Session: turn/start', - ' Driver->>Hooks: agent/prompt-submit waterfall', + ` Driver-->>SDK: ${mermaidCode('agent/status')} running`, + ` Driver->>Session: ${mermaidCode('turn/start')}`, + ` Driver->>Hooks: ${mermaidCode('agent/prompt-submit')} waterfall`, ' Hooks-->>Driver: allow, block, or add context', - ' Driver->>Session: user/message or rejected turn/end', - ' Driver->>Prompt: system-prompt/assemble waterfall', - ' Driver-->>Driver: agent/pre-step serial checkpoint', - ' Driver->>Session: step/start', - ' Driver->>LLM: agent/request waterfall, then llm/stream waterfall', + ` Driver->>Session: ${mermaidCode('user/message')} or rejected ${mermaidCode('turn/end')}`, + ` Driver->>Prompt: ${mermaidCode('system-prompt/assemble')} waterfall`, + ` Driver-->>Driver: ${mermaidCode('agent/pre-step')} serial checkpoint`, + ` Driver->>Session: ${mermaidCode('step/start')}`, + ` Driver->>LLM: ${mermaidCode('agent/request')} waterfall, then ${mermaidCode('llm/stream')} waterfall`, ' LLM-->>Driver: StreamChunk*', - ' Driver->>Session: assistant/chunk*', - ' Session-->>SDK: session/event assistant/chunk*', - ' Driver->>Hooks: agent/step-result waterfall', - ' Driver->>Session: assistant/message', - ' Driver->>Session: tool/call', + ` Driver->>Session: ${mermaidCode('assistant/chunk')}*`, + ` Session-->>SDK: ${mermaidCode('session/event')} ${mermaidCode('assistant/chunk')}*`, + ` Driver->>Hooks: ${mermaidCode('agent/step-result')} waterfall`, + ` Driver->>Session: ${mermaidCode('assistant/message')}`, + ` Driver->>Session: ${mermaidCode('tool/call')}`, ' Driver->>Tools: execute through pre and post waterfalls', ' Tools-->>Session: tool-owned events when applicable', - ' Driver->>Session: tool/result and step/end', - ' Driver->>Hooks: agent/turn-continuation waterfall', - ' Driver->>Session: turn/end', - ' Driver->>Persistence: session/flush parallel checkpoint', - ' Driver-->>SDK: agent/status idle', + ` Driver->>Session: ${mermaidCode('tool/result')} and ${mermaidCode('step/end')}`, + ` Driver->>Hooks: ${mermaidCode('agent/turn-continuation')} waterfall`, + ` Driver->>Session: ${mermaidCode('turn/end')}`, + ` Driver->>Persistence: ${mermaidCode('session/flush')} parallel checkpoint`, + ` Driver-->>SDK: ${mermaidCode('agent/status')} idle`, '```', '', 'SDK users that need replayable transcript data should consume `session/event`; `agent/*` is the live coordination surface for queue/status, prompt interception, request shaping, steering, continuation, and errors.', @@ -695,16 +610,16 @@ function renderToolPipeline(): string { '```mermaid', 'flowchart TD', ' model["Assistant message contains tool-call block"]', - ' toolCall["Session event: tool/call
logged before execution"]', + ` toolCall["Session event: ${mermaidCode('tool/call')}
logged before execution"]`, ' presentCall["UI pending card
presentCall(args)"]', - ' pre["tools/pre-execute waterfall
hooks, permission, sandbox"]', + ` pre["${mermaidCode('tools/pre-execute')} waterfall
hooks, permission, sandbox"]`, ' denied["deny or ask
tool body skipped"]', ' toolBody["Registered tool execute() body"]', - ' fsGate["fs/write-intent or fs/edit-intent
tool-fs mutations only"]', - ' owned["Tool-owned session events
todo/write, fs/observed, hook/invoked, hook/result"]', - ' post["tools/post-execute waterfall
accept, block, replace, add context"]', + ` fsGate["${mermaidCode('fs/write-intent')} or ${mermaidCode('fs/edit-intent')}
tool-fs mutations only"]`, + ` owned["Tool-owned session events
${mermaidCode('todo/write')}, ${mermaidCode('fs/observed')}, ${mermaidCode('hook/invoked')}, ${mermaidCode('hook/result')}"]`, + ` post["${mermaidCode('tools/post-execute')} waterfall
accept, block, replace, add context"]`, ' context["Buffered additionalContext
context/message after all tool results"]', - ' toolResult["Session event: tool/result
single model-facing outcome"]', + ` toolResult["Session event: ${mermaidCode('tool/result')}
single model-facing outcome"]`, ' presentResult["UI completed card
presentResult(args, result)"]', ' model --> toolCall', ' toolCall --> presentCall', @@ -726,82 +641,6 @@ function renderToolPipeline(): string { ].join('\n') } -function renderSessionSurface(): string { - return [ - ...generatedHeader('Session Surface And Message Projection', 'curated Mermaid dataflow; exact event/type shapes live in core-data-structures'), - 'This graph separates the append-only log from the derived message surface the next model request sees.', - '', - '```mermaid', - 'flowchart LR', - ' append["Session.append(type, data)"]', - ' log["Append-only SessionEvent log"]', - ' surface["SurfaceManager linked list
surfaceOp + sourceEventSeqs"]', - ' derive["deriveMessages()"]', - ' model["GenerateOptions.messages"]', - ' persist["JSONL / SQLite persistence"]', - ' replay["load / replay / fork seed"]', - ' append --> log', - ' log --> surface', - ' surface --> derive --> model', - ' log --> persist --> replay --> log', - '```', - '', - 'See [core-data-structures/session.md](../core-data-structures/session.md) for the full `SessionEventMap`, surface operations, and turn-enclosure invariant.', - '', - ].join('\n') -} - -function renderSubagentLineage(): string { - return [ - ...generatedHeader('Subagent And Session Lineage', 'curated Mermaid flow; provider inventory is visible in the generated capability seam graph'), - 'This graph keeps delegation semantics separate from hook observation. A subagent backend creates an ordinary child agent/session through the shared provider registry.', - '', - '```mermaid', - 'flowchart TD', - ' parent["Parent Agent + Session"]', - ' tool["tool-subagent
model-facing name"]', - ' registry["ctx.subagents provider registry"]', - ' spawn["spawn provider
fresh child session"]', - ' fork["fork provider
seeded from completed-turn prefix"]', - ' acp["ACP provider
out-of-process child"]', - ' child["Child AgentHandle
ordinary Agent lifecycle"]', - ' result["SubagentResult returned to tool"]', - ' parent --> tool --> registry', - ' registry --> spawn --> child', - ' registry --> fork --> child', - ' registry --> acp --> child', - ' child --> result --> parent', - '```', - '', - 'The hooks stack adds richer lifecycle observation around child runs; the core ownership rule stays the same: the provider owns the child handle and must dispose it.', - '', - ].join('\n') -} - -function renderHotReload(): string { - return [ - ...generatedHeader('Plugin Disposal And Hot Reload Ownership', 'curated Mermaid flow based on Cordis fiber/effect conventions'), - 'This graph is a maintainer checklist for plugin authors: registrations are effects, service injection gates activation, and owned handles must be disposed by their owner.', - '', - '```mermaid', - 'flowchart TD', - ' plugin["ctx.plugin(plugin) creates fiber"]', - ' inject["static inject gates activation"]', - ' service["ctx.provide / Service constructor"]', - ' effects["ctx.effect registrations
events, tools, adapters, timers"]', - ' reload["HMR / fiber.dispose()"]', - ' disposers["Run disposers in owner fiber"]', - ' quiescence["Owned AgentHandle.dispose()
or service teardown awaits quiescence"]', - ' plugin --> inject --> service', - ' inject --> effects', - ' reload --> disposers --> quiescence', - '```', - '', - 'Hook bridges and SDK plugins increase the number of long-lived listeners, so this ownership graph should stay small and visible.', - '', - ].join('\n') -} - function renderSnapshotReplay(): string { return [ ...generatedHeader('ACP Snapshot Replay', 'curated Mermaid sequence based on the snapshot test harness'), @@ -818,7 +657,7 @@ function renderSnapshotReplay(): string { ' Recorder->>Fixture: session.jsonl + workspace inputs', ' Fixture->>Workspace: seed files and hook configs', ' Fixture->>Replay: recorded StreamChunk script', - ' Replay->>ACP: deterministic llm/stream chunks', + ` Replay->>ACP: deterministic ${mermaidCode('llm/stream')} chunks`, ' ACP->>Workspace: bash, fs, and hook side effects', ' ACP->>Golden: normalized sessionUpdate stream', ' Golden-->>ACP: diff must be empty', @@ -829,72 +668,66 @@ function renderSnapshotReplay(): string { ].join('\n') } -async function renderDocs(): Promise { +function renderDocs(): GraphDoc[] { const pkgs = collectPackages() const docs: GraphDoc[] = [ - { rel: `${OUT_DIR}/package-topology.md`, content: renderPackageTopology(pkgs) }, - { rel: `${OUT_DIR}/capability-seams.md`, content: renderCapabilitySeams(pkgs) }, - { rel: `${OUT_DIR}/app-composition.md`, content: renderAppComposition() }, - { rel: `${OUT_DIR}/event-producer-consumer.md`, content: renderEventRelations(pkgs) }, - { rel: `${OUT_DIR}/tool-affordance-map.md`, content: await renderToolAffordance() }, - { rel: `${OUT_DIR}/agent-lifecycle.md`, content: renderLifecycle() }, - { rel: `${OUT_DIR}/tool-execution-pipeline.md`, content: renderToolPipeline() }, - { rel: `${OUT_DIR}/session-surface.md`, content: renderSessionSurface() }, - { rel: `${OUT_DIR}/subagent-lineage.md`, content: renderSubagentLineage() }, - { rel: `${OUT_DIR}/hot-reload-disposal.md`, content: renderHotReload() }, - { rel: `${OUT_DIR}/snapshot-replay.md`, content: renderSnapshotReplay() }, + { rel: 'docs/capability-seams.md', content: renderCapabilitySeams(pkgs) }, + ...APP_EXAMPLES.map(example => ({ rel: example.rel, content: renderAppComposition(example) })), + { rel: 'docs/event-producer-consumer.md', content: renderEventRelations(pkgs) }, + { rel: 'docs/agent-lifecycle.md', content: renderLifecycle() }, + { rel: 'docs/tool-execution-pipeline.md', content: renderToolPipeline() }, + { rel: 'docs/acp/snapshot-replay.md', content: renderSnapshotReplay() }, ] - docs.unshift({ rel: `${OUT_DIR}/README.md`, content: renderIndex(docs) }) + docs.unshift({ rel: 'docs/graph-atlas.md', content: renderIndex(docs) }) return docs } function renderIndex(docs: GraphDoc[]): string { const labels: Record = { - 'package-topology.md': 'package topology by group', - 'capability-seams.md': 'capability seams and core services', - 'app-composition.md': 'app composition', - 'event-producer-consumer.md': 'event producer/consumer matrix', - 'tool-affordance-map.md': 'tool affordance map', - 'agent-lifecycle.md': 'agent turn and step lifecycle', - 'tool-execution-pipeline.md': 'tool execution pipeline', - 'session-surface.md': 'session surface and message projection', - 'subagent-lineage.md': 'subagent and session lineage', - 'hot-reload-disposal.md': 'plugin disposal and hot reload ownership', - 'snapshot-replay.md': 'ACP snapshot replay', + 'docs/capability-seams.md': 'capability seams and core services', + 'docs/echo-agent-composition.md': 'echo-agent app composition', + 'docs/coding-agent-composition.md': 'coding-agent app composition', + 'docs/acp-agent-composition.md': 'acp-agent app composition', + 'docs/event-producer-consumer.md': 'event producer/consumer matrix', + 'docs/agent-lifecycle.md': 'agent turn and step lifecycle', + 'docs/tool-execution-pipeline.md': 'tool execution pipeline', + 'docs/acp/snapshot-replay.md': 'ACP snapshot replay', } const modes: Record = { - 'package-topology.md': 'generated', - 'capability-seams.md': 'hybrid generated', - 'app-composition.md': 'hybrid generated', - 'event-producer-consumer.md': 'hybrid generated', - 'tool-affordance-map.md': 'hybrid generated', - 'agent-lifecycle.md': 'curated', - 'tool-execution-pipeline.md': 'curated', - 'session-surface.md': 'curated', - 'subagent-lineage.md': 'curated', - 'hot-reload-disposal.md': 'curated', - 'snapshot-replay.md': 'curated', + 'docs/capability-seams.md': 'hybrid generated', + 'docs/echo-agent-composition.md': 'hybrid generated', + 'docs/coding-agent-composition.md': 'hybrid generated', + 'docs/acp-agent-composition.md': 'hybrid generated', + 'docs/event-producer-consumer.md': 'hybrid generated', + 'docs/agent-lifecycle.md': 'curated', + 'docs/tool-execution-pipeline.md': 'curated', + 'docs/acp/snapshot-replay.md': 'curated', } + const rows = [ + '| [module dependency graph](module-graph.md) | `generated` |', + '| [tool schema catalog and package map](tool-catalog/tools.md) | `generated` |', + ...docs.map((doc) => { + const link = doc.rel.replace(/^docs\//, '') + return `| [${labels[doc.rel] ?? link}](${link}) | \`${modes[doc.rel] ?? 'generated'}\` |` + }), + ] return [ - ...generatedHeader('Documentation Graph Atlas', 'mixed: each linked page declares generated, hybrid, or curated mode'), - 'The graph atlas is the relationship layer above the generated catalogs. Use it to navigate package topology, capability seams, event flow, model-facing tools, and runtime lifecycle paths. Exact signatures and type shapes still live in [cordis-catalog/](../cordis-catalog/events-and-services.md), [tool-catalog/](../tool-catalog/tools.md), and [core-data-structures/](../core-data-structures/core.md).', + ...generatedHeader('Documentation Graph Index', 'mixed: each linked page declares generated, hybrid, or curated mode'), + 'These diagrams are the relationship layer above the generated catalogs. Use them to navigate package topology, capability seams, event flow, model-facing tools, app composition, and runtime lifecycle paths. Exact signatures and type shapes still live in [cordis-catalog/](cordis-catalog/events-and-services.md), [tool-catalog/](tool-catalog/tools.md), and [core-data-structures/](core-data-structures/core.md).', '', - 'The process decision behind this atlas is recorded in [the documentation graph atlas RFC](../rfc/implemented/process/2026-07-03-documentation-graph-atlas.md).', + 'The process decision behind this index is recorded in [the documentation graph RFC](rfc/implemented/process/2026-07-03-documentation-graph-atlas.md).', '', '| Graph | Mode |', '| --- | --- |', - ...docs.map((doc) => { - const file = doc.rel.split('/').at(-1) ?? doc.rel - return `| [${labels[file] ?? file}](${file}) | \`${modes[file] ?? 'generated'}\` |` - }), + ...rows, '', 'Regenerate with `pnpm run gen-doc-graphs`; verify freshness with `pnpm run verify-doc-graphs`.', '', ].join('\n') } -async function main(): Promise { - const docs = await renderDocs() +function main(): void { + const docs = renderDocs() if (process.argv.includes('--check')) { const stale: string[] = [] for (const doc of docs) { @@ -910,11 +743,13 @@ async function main(): Promise { process.exit(1) } - mkdirSync(resolve(root, OUT_DIR), { recursive: true }) - for (const doc of docs) writeFileSync(resolve(root, doc.rel), doc.content) + for (const doc of docs) { + mkdirSync(dirname(resolve(root, doc.rel)), { recursive: true }) + writeFileSync(resolve(root, doc.rel), doc.content) + } console.log(`gen-doc-graphs: wrote ${docs.length} graph doc(s).`) } if (process.argv[1] && import.meta.filename === resolve(process.argv[1])) { - await main() + main() } diff --git a/scripts/gen-module-graph.ts b/scripts/gen-module-graph.ts index ebaf7d5db8..b84701c819 100644 --- a/scripts/gen-module-graph.ts +++ b/scripts/gen-module-graph.ts @@ -6,7 +6,8 @@ * these as `workspace:^` plus test-only extras, which would add noise). This * script reads every `packages/* /* /package.json`, keeps only the * `@deepseek-ai/dsh-*` peer edges (dropping the `cordis` peer), and renders a - * GitHub-viewable Mermaid graph plus a dependency table. + * GitHub-viewable Mermaid graph grouped by `packages//` plus a + * dependency table. * * The file is fully generated — never hand-edit it. Output is deterministic * (packages and edges sorted) so a regenerate-and-diff freshness check is @@ -17,8 +18,8 @@ * is stale (CI / pre-push gate) */ +import { dirname, resolve } from 'node:path' import { globSync, readFileSync, writeFileSync } from 'node:fs' -import { resolve } from 'node:path' const root = resolve(import.meta.dirname, '..') const OUT = 'docs/module-graph.md' @@ -27,10 +28,30 @@ const SCOPE = '@deepseek-ai/dsh-' interface Pkg { /** Short name, `@deepseek-ai/dsh-` prefix stripped (e.g. `agent-loop`). */ short: string + /** Package group from `packages//`. */ + group: string + /** Repo-relative package directory. */ + rel: string /** Short names of this package's in-repo peer dependencies, sorted. */ deps: string[] } +const GROUP_ORDER = [ + 'util', + 'llm', + 'core', + 'bash', + 'fs', + 'compact', + 'subagent', + 'web', + 'todo', + 'hooks', + 'session-persistence', + 'support', + 'ui', +] + /** Read every workspace package and its `@deepseek-ai/dsh-*` peer edges. */ function collect(): Pkg[] { const pkgs: Pkg[] = [] @@ -44,7 +65,9 @@ function collect(): Pkg[] { .filter(d => d.startsWith(SCOPE)) .map(d => d.slice(SCOPE.length)) .sort() - pkgs.push({ short: json.name.slice(SCOPE.length), deps }) + const [, group, leaf] = rel.split('/') + if (group === undefined || leaf === undefined) throw new Error(`gen-module-graph: unexpected package path ${rel}`) + pkgs.push({ short: json.name.slice(SCOPE.length), group, rel: dirname(rel), deps }) } return topoSort(pkgs) } @@ -63,7 +86,7 @@ function topoSort(pkgs: Pkg[]): Pkg[] { while (remaining.size > 0) { const ready = [...remaining.values()] .filter(p => p.deps.every(d => placed.has(d))) - .sort((a, b) => a.short.localeCompare(b.short)) + .sort(comparePackages) if (ready.length === 0) throw new Error(`gen-module-graph: dependency cycle among ${[...remaining.keys()].join(', ')}`) for (const p of ready) { out.push(p) @@ -74,28 +97,71 @@ function topoSort(pkgs: Pkg[]): Pkg[] { return out } +function comparePackages(a: Pkg, b: Pkg): number { + const groupA = GROUP_ORDER.indexOf(a.group) + const groupB = GROUP_ORDER.indexOf(b.group) + const normA = groupA === -1 ? Number.MAX_SAFE_INTEGER : groupA + const normB = groupB === -1 ? Number.MAX_SAFE_INTEGER : groupB + return normA - normB || a.group.localeCompare(b.group) || a.short.localeCompare(b.short) +} + +function nodeId(prefix: string, value: string): string { + return `${prefix}_${value.replace(/[^a-zA-Z0-9_]/g, '_')}` +} + +function escLabel(value: string): string { + return value.replace(/"/g, '\\"') +} + +function packageLink(pkg: Pkg): string { + return `[\`${pkg.short}\`](../${pkg.rel})` +} + /** Render the full docs/module-graph.md content (pure, deterministic). */ function render(pkgs: Pkg[]): string { const edges: string[] = [] for (const p of pkgs) { - for (const d of p.deps) edges.push(` ${p.short} --> ${d}`) + for (const d of p.deps) edges.push(` ${nodeId('pkg', p.short)} --> ${nodeId('pkg', d)}`) } - const rows = pkgs.map(p => `| \`${p.short}\` | ${p.deps.length ? p.deps.map(d => `\`${d}\``).join(', ') : '—'} |`) + const byShort = new Map(pkgs.map(pkg => [pkg.short, pkg])) + const groups = [...new Set(pkgs.map(pkg => pkg.group))].sort((a, b) => { + const ia = GROUP_ORDER.indexOf(a) + const ib = GROUP_ORDER.indexOf(b) + const na = ia === -1 ? Number.MAX_SAFE_INTEGER : ia + const nb = ib === -1 ? Number.MAX_SAFE_INTEGER : ib + return na - nb || a.localeCompare(b) + }) + const groupBlocks: string[] = [] + for (const group of groups) { + groupBlocks.push(` subgraph ${nodeId('group', group)}["packages/${escLabel(group)}"]`) + for (const pkg of pkgs.filter(p => p.group === group).sort((a, b) => a.short.localeCompare(b.short))) { + groupBlocks.push(` ${nodeId('pkg', pkg.short)}["${escLabel(pkg.short)}"]`) + } + groupBlocks.push(' end') + } + const rows = pkgs.map((p) => { + const deps = p.deps.length ? p.deps.map((d) => { + const dep = byShort.get(d) + return dep ? packageLink(dep) : `\`${d}\`` + }).join(', ') : '—' + return `| ${packageLink(p)} | \`${p.group}\` | ${deps} |` + }) return [ '', '', '# Module dependency graph', '', - 'Inter-package dependencies among the `@deepseek-ai/dsh-*` harness packages, derived from each package\'s `peerDependencies` (the canonical runtime-dependency signal). An edge `a --> b` means package `a` depends on package `b`. Names have the `@deepseek-ai/dsh-` prefix stripped.', + 'Inter-package dependencies among the `@deepseek-ai/dsh-*` harness packages, derived from each package\'s `peerDependencies` (the canonical runtime-dependency signal) and grouped by the `packages//` hierarchy. An edge `a --> b` means package `a` depends on package `b`. Names have the `@deepseek-ai/dsh-` prefix stripped.', '', '```mermaid', - 'graph TD', + 'flowchart TD', + ...groupBlocks, ...edges, '```', '', - '| Package | Depends on |', - '| --- | --- |', + '| Package | Group | Depends on |', + '| --- | --- | --- |', ...rows, '', ].join('\n') diff --git a/scripts/gen-tool-catalog.ts b/scripts/gen-tool-catalog.ts index 3298c507f8..e42754bf53 100644 --- a/scripts/gen-tool-catalog.ts +++ b/scripts/gen-tool-catalog.ts @@ -75,6 +75,12 @@ interface ToolPackage { dir: string /** Repo-relative source path linked from the catalog entry. */ source: string + /** Services or owning runtime surfaces the package requires at execution time. */ + requires: string[] + /** Session events or other visible state the tools write or affect. */ + writes: string[] + /** Additional model-visible names shipped by example/app config. */ + shippedNames?: string[] /** Plug the injected seams + the tool plugin onto a context that already * carries `systemPrompt` + `tools`. */ mount: (ctx: Context) => Promise @@ -98,15 +104,21 @@ const TOOL_PACKAGES: ToolPackage[] = [ pkg: '@deepseek-ai/dsh-tool-bash', dir: 'tool-bash', source: 'packages/bash/tool-bash/src/index.ts', + requires: ['ctx.tools', 'ctx.bash'], + writes: ['tool/call', 'tool/result', 'context/message via agent.inject() for background completion notices'], async mount(ctx) { await ctx.plugin(LocalBashExecutor) await ctx.plugin(ToolBash) }, + note: + 'The bash/bash_output/bash_kill tools are model-facing consumers of the bash executor seam.', }, { pkg: '@deepseek-ai/dsh-tool-fs', dir: 'tool-fs', source: 'packages/fs/tool-fs/src/index.ts', + requires: ['ctx.tools', 'ctx.fs', 'ctx.systemPrompt'], + writes: ['tool/call', 'fs/write-intent or fs/edit-intent for mutations', 'fs/observed after successful file operations', 'tool/result'], async mount(ctx) { // The tool injects `fs`; boot the local backend to satisfy it. The schemas // do not depend on the policy plugin (an event gate that changes behavior, @@ -121,6 +133,9 @@ const TOOL_PACKAGES: ToolPackage[] = [ pkg: '@deepseek-ai/dsh-tool-subagent', dir: 'tool-subagent', source: 'packages/subagent/tool-subagent/src/index.ts', + requires: ['ctx.tools', 'ctx.subagents'], + writes: ['tool/call', 'tool/result', 'child session events through the chosen provider'], + shippedNames: ['subagent', 'subagent_fork'], async mount(ctx) { await ctx.plugin(SubagentService) // Register a scripted provider under the name the tool delegates to. @@ -134,14 +149,20 @@ const TOOL_PACKAGES: ToolPackage[] = [ pkg: '@deepseek-ai/dsh-tool-todo', dir: 'tool-todo', source: 'packages/todo/tool-todo/src/index.ts', + requires: ['ctx.tools', 'owning Agent session'], + writes: ['tool/call', 'todo/write', 'tool/result'], async mount(ctx) { await ctx.plugin(ToolTodo) }, + note: + 'todo_write is session-owned state; UIs render the latest todo/write event as a checklist or ACP plan.', }, { pkg: '@deepseek-ai/dsh-tool-web', dir: 'tool-web', source: 'packages/web/tool-web/src/index.ts', + requires: ['ctx.tools', 'ctx.web', 'ctx.systemPrompt'], + writes: ['tool/call', 'tool/result'], async mount(ctx) { // The tools inject `web`; boot the seam plus one search and one fetch // provider so both `web_search` and `web_fetch` register. The schemas do @@ -152,6 +173,8 @@ const TOOL_PACKAGES: ToolPackage[] = [ await ctx.plugin(WebFetchLocal) await ctx.plugin(ToolWeb) }, + note: + 'web_search and web_fetch keep provider selection behind ctx.web so model-visible schemas stay stable across backend swaps.', }, ] @@ -159,6 +182,9 @@ const TOOL_PACKAGES: ToolPackage[] = [ interface CatalogPackage { pkg: string source: string + requires: string[] + writes: string[] + shippedNames?: string[] schemas: ToolSchema[] /** A deployment note (see {@link ToolPackage.note}), rendered after the tools. */ note?: string @@ -208,7 +234,15 @@ export async function collectToolCatalog(packages: ToolPackage[] = TOOL_PACKAGES await ctx.plugin(ToolRegistry) await entry.mount(ctx) const schemas = ctx.tools.schemas().sort((a, b) => a.name.localeCompare(b.name)) - catalog.push({ pkg: entry.pkg, source: entry.source, schemas, ...entry.note !== undefined ? { note: entry.note } : {} }) + catalog.push({ + pkg: entry.pkg, + source: entry.source, + requires: entry.requires, + writes: entry.writes, + schemas, + ...entry.shippedNames !== undefined ? { shippedNames: entry.shippedNames } : {}, + ...entry.note !== undefined ? { note: entry.note } : {}, + }) } finally { await ctx.fiber.dispose() } @@ -226,6 +260,14 @@ function renderTool(schema: ToolSchema, source: string): string[] { return out } +function codeList(values: string[] | undefined): string { + return values?.length ? values.map(value => `\`${value}\``).join(', ') : '-' +} + +function tableCell(value: string | undefined): string { + return value ? value.replace(/\|/g, '\\|').replace(/\n/g, '
') : '-' +} + /** Render the full catalog (pure, deterministic given the manifest-ordered input). */ export function render(catalog: ToolCatalog): string { const lines: string[] = [ @@ -240,6 +282,14 @@ export function render(catalog: ToolCatalog): string { '', 'Scope: shipped product tools under `packages/*/tool-*`, each booted with its DEFAULT config. The registered tool NAME can be a load-time config (e.g. `tool-subagent`\'s `toolName`), so a deployment may surface a package under a different or additional name — a per-package note records those shipped aliases where they exist. The `examples/` demo tools (e.g. `echo`) are excluded, matching the cordis catalog\'s packages-only scope.', '', + '## Tool Package Map', + '', + 'This table connects model-visible tool names to the plugin package and service seams behind them. Exact JSON Schemas follow in the package sections below.', + '', + '| Tool package | Model-visible names | Requires | Writes / affects | Shipped aliases | Deployment note |', + '| --- | --- | --- | --- | --- | --- |', + ...catalog.map(entry => `| \`${entry.pkg}\` | ${codeList(entry.schemas.map(schema => schema.name))} | ${codeList(entry.requires)} | ${codeList(entry.writes)} | ${codeList(entry.shippedNames)} | ${tableCell(entry.note)} |`), + '', ] for (const entry of catalog) { lines.push(`## \`${entry.pkg}\``, '') diff --git a/scripts/verify-mermaid.ts b/scripts/verify-mermaid.ts index ee27bbeee3..954c246640 100644 --- a/scripts/verify-mermaid.ts +++ b/scripts/verify-mermaid.ts @@ -5,9 +5,9 @@ * render. * * Scope matches the Markdown link gate so any Mermaid diagram in repo-authored - * docs is checked: README.md, docs/** /*.md, packages/* /*.md, - * packages/* /* /*.md, examples/** /*.md, AGENTS.md, packages/AGENTS.md, and - * .agents/skills/** /*.md. + * docs is checked: README.md, README.zh.md, docs/** /*.md, + * packages/* /*.md, packages/* /* /*.md, examples/** /*.md, AGENTS.md, + * packages/AGENTS.md, and .agents/skills/** /*.md. * * Run: `tsx scripts/verify-mermaid.ts`. */ @@ -25,6 +25,7 @@ const root = resolve(import.meta.dirname, '..') const PATTERNS = [ 'README.md', + 'README.zh.md', 'docs/**/*.md', 'packages/*/*.md', 'packages/*/*/*.md', From cc59aede967bba05bf53436fa468407170577033 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 5 Jul 2026 01:29:47 +0800 Subject: [PATCH 07/14] docs: fix two references left stale by the Layering removal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Self-review sweep on the section NAME (not just anchors) found: the docs index row in docs/AGENTS.md still advertised 'layering' among architecture.md's contents, and the extract-example-app-packages RFC cited 'docs/architecture.md § Layering' for the dependency rule, which now lives under § Service map. --- docs/AGENTS.md | 2 +- .../architecture/2026-06-20-extract-example-app-packages.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/AGENTS.md b/docs/AGENTS.md index b1df4a02d9..bff58f4013 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -10,7 +10,7 @@ Every fact has exactly one home — the tier whose job it is — and every other |---|---|---| | Root `AGENTS.md` | Standing orders: rules an agent needs in context in every session, one to three lines each, linking its home | Stories, worked examples, situational procedures, anything restated from a linked home | | Subtree `AGENTS.md` (`packages/`, `examples/`, `docs/`) | Orders specific to that subtree | Repo-wide rules the root file already carries | -| [architecture.md](architecture.md) | The system map: layering, services, the loop, extension seams — read before changing `packages/` | Type shapes (→ core-data-structures), per-package detail (→ package READMEs), decision rationale (→ RFCs), implementation-status annotations | +| [architecture.md](architecture.md) | The system map: services, the loop, extension seams — read before changing `packages/` | Type shapes (→ core-data-structures), per-package detail (→ package READMEs), decision rationale (→ RFCs), implementation-status annotations | | [core-data-structures/](core-data-structures/core.md) | The type catalog: literal shapes and semantics of the spine and seam vocabulary | Behavior narration (→ architecture.md) | | [rfc/](rfc/README.md) | Decision records: the why and the what-was-given-up; `implemented/` RFCs describe shipped reality in present tense | Migration plans, test checklists, and spec-speak ("should…") once the decision has shipped | | [postmortem/](postmortem/README.md) | Incident stories — the only tier where war-story narrative belongs | — | diff --git a/docs/rfc/implemented/architecture/2026-06-20-extract-example-app-packages.md b/docs/rfc/implemented/architecture/2026-06-20-extract-example-app-packages.md index 380586699b..b0d78bdc84 100644 --- a/docs/rfc/implemented/architecture/2026-06-20-extract-example-app-packages.md +++ b/docs/rfc/implemented/architecture/2026-06-20-extract-example-app-packages.md @@ -12,7 +12,7 @@ The deeper problem was a **coupled front-door cluster** that lived at the leaf w Each example is now **mostly an invocation of an app package**, splitting the wiring along the existing [interface / implementation / consumer seam](2026-06-13-capability-seams.md): the **app package owns the composition**, the leaf `cordis.yml` owns only the **swappable choices** (which LLM adapter, which bash executor, model, prompt, persistence root). -- **`@deepseek-ai/dsh-agent-core`** ([packages/core/agent-core](../../../../packages/core/agent-core)) — a Cordis bundle plugin for the providerless, executor-less, UI-less spine: `timer` + `llm` + sessions + system-prompt + tools + agents + invariants + `tool-bash` + `agent-loop`, mounted as child plugins inside its `apply(ctx)` via `ctx.plugin(...)`. This is the old `base-core.yml` **minus** `bash-local`, **plus** `timer` and the loop, as code instead of a YAML include. The bundle **forwards** `agent-loop`'s `agents` list as its own config (`export const Config = AgentLoop.Config`, default `[]`, the existing `AgentLoop.Config` shape in [packages/core/agent-loop/src/index.ts](../../../../packages/core/agent-loop/src/index.ts)) — so each app supplies its own pre-created agents. This is precisely the reason the old `base-core.yml` gave for keeping `agent-loop` *out* of the shared core ("the examples disagree — stdio needs a pre-created `main`, acp needs none"); forwarding the config dissolves that objection — the loop is shared, the agents list is per-app. The bundle children register into the root service store, so a leaf-mounted sibling (the adapter, the executor) sees them exactly as a nested `plugin-include` subtree's services were seen before. Depending on the CONCRETE `dsh-agent-loop` (not just the `dsh-agent` interface) is deliberate and is the sanctioned exception to the "extension plugins depend on interfaces, never on the concrete loop" rule (packages/README.md, docs/architecture.md § Layering): the rule constrains plugins that EXTEND the system, whereas this bundle's whole job is to COMPOSE the concrete spine. Swapping the loop means publishing a different bundle, not rewiring every extension. +- **`@deepseek-ai/dsh-agent-core`** ([packages/core/agent-core](../../../../packages/core/agent-core)) — a Cordis bundle plugin for the providerless, executor-less, UI-less spine: `timer` + `llm` + sessions + system-prompt + tools + agents + invariants + `tool-bash` + `agent-loop`, mounted as child plugins inside its `apply(ctx)` via `ctx.plugin(...)`. This is the old `base-core.yml` **minus** `bash-local`, **plus** `timer` and the loop, as code instead of a YAML include. The bundle **forwards** `agent-loop`'s `agents` list as its own config (`export const Config = AgentLoop.Config`, default `[]`, the existing `AgentLoop.Config` shape in [packages/core/agent-loop/src/index.ts](../../../../packages/core/agent-loop/src/index.ts)) — so each app supplies its own pre-created agents. This is precisely the reason the old `base-core.yml` gave for keeping `agent-loop` *out* of the shared core ("the examples disagree — stdio needs a pre-created `main`, acp needs none"); forwarding the config dissolves that objection — the loop is shared, the agents list is per-app. The bundle children register into the root service store, so a leaf-mounted sibling (the adapter, the executor) sees them exactly as a nested `plugin-include` subtree's services were seen before. Depending on the CONCRETE `dsh-agent-loop` (not just the `dsh-agent` interface) is deliberate and is the sanctioned exception to the "extension plugins depend on interfaces, never on the concrete loop" rule (packages/README.md, docs/architecture.md § Service map): the rule constrains plugins that EXTEND the system, whereas this bundle's whole job is to COMPOSE the concrete spine. Swapping the loop means publishing a different bundle, not rewiring every extension. - **`@deepseek-ai/dsh-stdio-agent`** ([packages/ui/stdio-agent](../../../../packages/ui/stdio-agent)) and **`@deepseek-ai/dsh-acp-agent`** ([packages/ui/acp-agent](../../../../packages/ui/acp-agent)) — app packages, each consuming `dsh-agent-core` and **baking in its coupled front-door cluster**: stdio = `ui-stdio` + console logger + a pre-created `main`; acp = the `acp` bridge + JSONL persistence + **no stdout logger** + no pre-created agents. The leaf no longer carries the cluster, so it has no logger entry to copy wrong by default — the common stdout-purity mistake loses its foothold. (A leaf can still *add* a sibling logger entry — a package cannot forbid what a leaf author writes — so the rule "never add a stdout logger to an ACP leaf" stays documented at the leaf; what changed is that the default leaf has nothing to get wrong.) They land under the existing `ui` group alongside `acp`, so no new package group (and no `tsconfig`/`packages/README` group plumbing) was needed. - **`start.ts` is gone.** Each app package exposes a `bin` (`dsh-stdio-agent` / `dsh-acp-agent`); the `demo:*` scripts invoke it (e.g. `dsh-stdio-agent ./cordis.yml`). The Loader-boot tail, `.env` loading, and fail-loud guards live in the shared [`@deepseek-ai/dsh-app-boot`](../../../../packages/ui/app-boot) package (unit-tested under the per-file coverage gate — see [share the app bins' boot glue](../simplification/2026-07-04-share-app-bin-boot-glue.md)); each bin is a thin self-executing composition over those helpers plus its app-specific lifecycle (the ACP bin: snapshot-mode selection and stdin-dispose). The `bin.ts` files themselves stay coverage-excluded (self-executing CLI entries, like the old `start.ts`) and are driven by the keyless Loader-path tests. - **Each leaf `cordis.yml` collapses** to backends + config: the LLM adapter (`llm-deepseek` with apiKey/models, or `llm-replay`), the bash executor (`bash-local`), `hmr` for the stdio demos (see the amendment below), and one app entry carrying the app's config (model, system prompt, persistence root — surfaced as the app package's own `Config`, which routes each value to wherever the app wires it: stdio onto its pre-created agent, acp onto the bridge plugin). From 60b097c829ec4a69607241883a9a7f48aac4d469 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 5 Jul 2026 02:32:28 +0800 Subject: [PATCH 08/14] docs: drop all Feishu references The repo's docs are read by people and agents without access to the company wiki, so external Feishu links are dead weight: the two design-doc links (MVP requirements, microkernel implementation notes) leave AGENTS.md's intro and docs/architecture.md (usages + link definitions), and the microkernel-event-taxonomy RFC drops its plain-text pointer to the same design doc. The governing principle each reference decorated is already stated in place; no content moves. --- AGENTS.md | 2 +- docs/architecture.md | 7 ++----- .../architecture/2026-06-11-microkernel-event-taxonomy.md | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 5023fd5c5e..2832af8cda 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # AGENTS.md -This is the monorepo of the DeepSeek Harness group; it hosts **DeepSeek Code**, DeepSeek's coding agent product. The codebase is built on the vendored Cordis framework, microkernel-style: **everything is a plugin**. Read [docs/architecture.md](docs/architecture.md) before changing anything under `packages/` — the service map, event taxonomy, loop lifecycle, and extension seams. The documentation standard is [docs/AGENTS.md](docs/AGENTS.md). Design context: [Coding Harness MVP 需求分析](https://trtgsjkv6r.feishu.cn/wiki/ZwK6wfBE9i91V6kzMGYcgRGanxg), [微内核Harness实现思路](https://trtgsjkv6r.feishu.cn/wiki/VS9Lw1kQki6mDJk2UHocyuphnsc). +This is the monorepo of the DeepSeek Harness group; it hosts **DeepSeek Code**, DeepSeek's coding agent product. The codebase is built on the vendored Cordis framework, microkernel-style: **everything is a plugin**. Read [docs/architecture.md](docs/architecture.md) before changing anything under `packages/` — the service map, event taxonomy, loop lifecycle, and extension seams. The documentation standard is [docs/AGENTS.md](docs/AGENTS.md). ## Pre-release stance: foundation over blast radius diff --git a/docs/architecture.md b/docs/architecture.md index 8be94684a5..83c07aaba2 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,11 +1,8 @@ # DeepSeek Harness Architecture -This document describes the architecture of the DeepSeek Harness — the foundation of **DeepSeek Code**. The governing principle, from the [microkernel design discussion][microkernel-doc]: **everything is a plugin**. The core is deliberately tiny — a handful of abstract services plus one concrete loop plugin (`dsh-agent-loop`) — and every product feature is a plugin against the extension surface described here, without modifying the loop. The stack is three tiers: plugins (the loop itself, seam implementations, model-facing tools, bridges) over interface/service packages (each owning one `ctx` key and its vocabulary) over the vendored Cordis kernel (`vendor/`). +This document describes the architecture of the DeepSeek Harness — the foundation of **DeepSeek Code**. The governing principle: **everything is a plugin**. The core is deliberately tiny — a handful of abstract services plus one concrete loop plugin (`dsh-agent-loop`) — and every product feature is a plugin against the extension surface described here, without modifying the loop. The stack is three tiers: plugins (the loop itself, seam implementations, model-facing tools, bridges) over interface/service packages (each owning one `ctx` key and its vocabulary) over the vendored Cordis kernel (`vendor/`). -This document covers **behavior**; type shapes live in [core-data-structures/](core-data-structures/core.md), the per-event/service reference in the generated [events](cordis-catalog/events.md) / [services](cordis-catalog/services.md) catalogs, per-package contracts in the package READMEs ([map](../packages/README.md)). Requirement context: [Coding Harness MVP 需求分析][mvp-doc]. - -[microkernel-doc]: https://trtgsjkv6r.feishu.cn/wiki/VS9Lw1kQki6mDJk2UHocyuphnsc -[mvp-doc]: https://trtgsjkv6r.feishu.cn/wiki/ZwK6wfBE9i91V6kzMGYcgRGanxg +This document covers **behavior**; type shapes live in [core-data-structures/](core-data-structures/core.md), the per-event/service reference in the generated [events](cordis-catalog/events.md) / [services](cordis-catalog/services.md) catalogs, per-package contracts in the package READMEs ([map](../packages/README.md)). ## Service map diff --git a/docs/rfc/implemented/architecture/2026-06-11-microkernel-event-taxonomy.md b/docs/rfc/implemented/architecture/2026-06-11-microkernel-event-taxonomy.md index 227a72bc40..9dc91ae6da 100644 --- a/docs/rfc/implemented/architecture/2026-06-11-microkernel-event-taxonomy.md +++ b/docs/rfc/implemented/architecture/2026-06-11-microkernel-event-taxonomy.md @@ -6,7 +6,7 @@ Status: implemented (accepted 2026-06-11) ## Context -The product principle (see the 微内核Harness实现思路 design doc) is "everything is a plugin": hooks, /goal, /loop, dynamic workflows, compaction, sandboxing, permissions, UI, persistence, MCP, skills must all be writable as plugins without modifying the core. Candidate mechanisms considered: a purpose-built middleware stack (koa-compose style), an explicit phase state machine plugins can insert into, or Cordis's native event system. +The product principle is "everything is a plugin": hooks, /goal, /loop, dynamic workflows, compaction, sandboxing, permissions, UI, persistence, MCP, skills must all be writable as plugins without modifying the core. Candidate mechanisms considered: a purpose-built middleware stack (koa-compose style), an explicit phase state machine plugins can insert into, or Cordis's native event system. ## Decision From 54a3c8b2a426a0e875382f70b4e0d7b6df30bf6b Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 5 Jul 2026 02:54:01 +0800 Subject: [PATCH 09/14] docs: address graph review placement --- docs/acp/snapshot-replay.md | 4 +- docs/agent-lifecycle.md | 4 +- docs/capability-seams.md | 4 +- docs/event-producer-consumer.md | 4 +- docs/graph-atlas.md | 10 +-- .../2026-07-03-documentation-graph-atlas.md | 6 +- docs/tool-execution-pipeline.md | 4 +- .../acp-agent/composition.md | 6 +- .../coding-agent/composition.md | 6 +- .../echo-agent/composition.md | 6 +- scripts/gen-doc-graphs.ts | 73 ++++++++++++------- 11 files changed, 74 insertions(+), 53 deletions(-) rename docs/acp-agent-composition.md => examples/acp-agent/composition.md (97%) rename docs/coding-agent-composition.md => examples/coding-agent/composition.md (97%) rename docs/echo-agent-composition.md => examples/echo-agent/composition.md (95%) diff --git a/docs/acp/snapshot-replay.md b/docs/acp/snapshot-replay.md index 0acf3c0a96..4242f2406d 100644 --- a/docs/acp/snapshot-replay.md +++ b/docs/acp/snapshot-replay.md @@ -3,8 +3,6 @@ # ACP Snapshot Replay -Maintenance mode: curated Mermaid sequence based on the snapshot test harness. - This graph explains what a snapshot scenario proves: recorded real-model session logs are replayed keylessly, ACP stdout is normalized and diffed, and scenario workspaces preserve tool side effects that the UI stream alone cannot prove. ```mermaid @@ -25,3 +23,5 @@ sequenceDiagram ``` The fs and hook snapshot matrix is valuable because it proves world state, hook decisions, and failed tool-card rendering, not just that replay returns text. + +Maintenance mode: curated Mermaid sequence based on the snapshot test harness. diff --git a/docs/agent-lifecycle.md b/docs/agent-lifecycle.md index eb2744f18a..d6d9ab1ba7 100644 --- a/docs/agent-lifecycle.md +++ b/docs/agent-lifecycle.md @@ -3,8 +3,6 @@ # Agent Turn And Step Lifecycle -Maintenance mode: curated Mermaid sequence; exact event signatures live in the generated Cordis catalog. - This sequence is the visual companion to [architecture.md](architecture.md#loop-lifecycle-session--turn--step). It keeps durable replay facts on `session/event` and live control/status on `agent/*`. ```mermaid @@ -47,3 +45,5 @@ sequenceDiagram ``` SDK users that need replayable transcript data should consume `session/event`; `agent/*` is the live coordination surface for queue/status, prompt interception, request shaping, steering, continuation, and errors. + +Maintenance mode: curated Mermaid sequence; exact event signatures live in the generated Cordis catalog. diff --git a/docs/capability-seams.md b/docs/capability-seams.md index 7af76d043b..414ee898d1 100644 --- a/docs/capability-seams.md +++ b/docs/capability-seams.md @@ -3,8 +3,6 @@ # Capability Seams And Core Services -Maintenance mode: hybrid: services are discovered from Cordis declarations; interface/implementation/consumer roles are classified in `scripts/gen-doc-graphs.ts` with a completeness guard. - A service can be a core spine service, a swappable capability seam, or a bundle/composition point. The graph shows the package that owns the service declaration, known implementation packages, and packages that consume the service directly. ```mermaid @@ -140,3 +138,5 @@ flowchart LR | `ctx.compact` | `seam` | [`compact`](../packages/compact/compact) | [`compact-basic`](../packages/compact/compact-basic) | [`compact-basic`](../packages/compact/compact-basic) | - | The basic backend currently consumes the pre-step event directly; a model-facing compact tool remains deferred. | | `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn`](../packages/subagent/subagent-spawn), [`subagent-fork`](../packages/subagent/subagent-fork), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-mock`](../packages/support/subagent-mock) | [`tool-subagent`](../packages/subagent/tool-subagent) | - | Providers implement transports; tool-subagent exposes one configured provider as a model-facing tool name. | | `ctx.web` | `seam` | [`web`](../packages/web/web) | [`web-search-exa`](../packages/web/web-search-exa), [`web-search-perplexity`](../packages/web/web-search-perplexity), [`web-search-deepseek`](../packages/web/web-search-deepseek), [`web-fetch-local`](../packages/web/web-fetch-local) | [`tool-web`](../packages/web/tool-web) | - | Search and fetch providers register into one ctx.web seam; tool-web owns the stable model-facing names. | + +Maintenance mode: hybrid: services are discovered from Cordis declarations; interface/implementation/consumer roles are classified in `scripts/gen-doc-graphs.ts` with a completeness guard. diff --git a/docs/event-producer-consumer.md b/docs/event-producer-consumer.md index 1ac69ffd90..5460f662d4 100644 --- a/docs/event-producer-consumer.md +++ b/docs/event-producer-consumer.md @@ -3,8 +3,6 @@ # Event Producer And Consumer Matrix -Maintenance mode: hybrid generated: Cordis event declarations and most producer/listener edges are AST-scanned; dynamic dispatch sites are classified in `scripts/gen-doc-graphs.ts`. - This matrix shows which packages dispatch each harness-owned event and which packages listen to it. It is intentionally a table rather than one large graph: events are many-to-many, and dense relation data is easier to review in rows. Dynamic dispatch overrides cover sites that deliberately bypass `ctx.emit`, such as subagent lifecycle containment. | Event | Mode | Declared in | Dispatchers | Listeners | @@ -34,3 +32,5 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `tools/change` | `emit` | [`packages/core/tools/src/index.ts:87`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`emit`) | - | | `tools/post-execute` | `waterfall` | [`packages/core/tools/src/index.ts:82`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) | | `tools/pre-execute` | `waterfall` | [`packages/core/tools/src/index.ts:66`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) | + +Maintenance mode: hybrid generated: Cordis event declarations and most producer/listener edges are AST-scanned; dynamic dispatch sites are classified in `scripts/gen-doc-graphs.ts`. diff --git a/docs/graph-atlas.md b/docs/graph-atlas.md index c99b54dce6..9fb678d3d7 100644 --- a/docs/graph-atlas.md +++ b/docs/graph-atlas.md @@ -3,8 +3,6 @@ # Documentation Graph Index -Maintenance mode: mixed: each linked page declares generated, hybrid, or curated mode. - These diagrams are the relationship layer above the generated catalogs. Use them to navigate package topology, capability seams, event flow, model-facing tools, app composition, and runtime lifecycle paths. Exact signatures and type shapes still live in the generated [events](cordis-catalog/events.md) / [services](cordis-catalog/services.md) catalogs, [tool-catalog/](tool-catalog/tools.md), and [core-data-structures/](core-data-structures/core.md). The process decision behind this index is recorded in [the documentation graph RFC](rfc/implemented/process/2026-07-03-documentation-graph-atlas.md). @@ -14,12 +12,14 @@ The process decision behind this index is recorded in [the documentation graph R | [module dependency graph](module-graph.md) | `generated` | | [tool schema catalog and package map](tool-catalog/tools.md) | `generated` | | [capability seams and core services](capability-seams.md) | `hybrid generated` | -| [echo-agent app composition](echo-agent-composition.md) | `hybrid generated` | -| [coding-agent app composition](coding-agent-composition.md) | `hybrid generated` | -| [acp-agent app composition](acp-agent-composition.md) | `hybrid generated` | +| [echo-agent app composition](../examples/echo-agent/composition.md) | `hybrid generated` | +| [coding-agent app composition](../examples/coding-agent/composition.md) | `hybrid generated` | +| [acp-agent app composition](../examples/acp-agent/composition.md) | `hybrid generated` | | [event producer/consumer matrix](event-producer-consumer.md) | `hybrid generated` | | [agent turn and step lifecycle](agent-lifecycle.md) | `curated` | | [tool execution pipeline](tool-execution-pipeline.md) | `curated` | | [ACP snapshot replay](acp/snapshot-replay.md) | `curated` | Regenerate with `pnpm run gen-doc-graphs`; verify freshness with `pnpm run verify-doc-graphs`. + +Maintenance mode: mixed: each linked page declares generated, hybrid, or curated mode. diff --git a/docs/rfc/implemented/process/2026-07-03-documentation-graph-atlas.md b/docs/rfc/implemented/process/2026-07-03-documentation-graph-atlas.md index ad3e90a16f..c8d46c6fd2 100644 --- a/docs/rfc/implemented/process/2026-07-03-documentation-graph-atlas.md +++ b/docs/rfc/implemented/process/2026-07-03-documentation-graph-atlas.md @@ -33,9 +33,9 @@ The first index links ten relationship surfaces. Package topology and tool-packa | [module dependency graph](../../../module-graph.md) | generated | `packages/*/*/package.json` peer dependencies plus package group paths | | [tool schema catalog and package map](../../../tool-catalog/tools.md) | generated | boot-harvested tool schemas plus tool-package service/effect metadata | | [capability seams and core services](../../../capability-seams.md) | hybrid generated | Cordis service declarations plus a role manifest in `gen-doc-graphs.ts` | -| [echo-agent app composition](../../../echo-agent-composition.md) | hybrid generated | `examples/echo-agent/cordis.yml` plugin list plus curated app/bundle expansion | -| [coding-agent app composition](../../../coding-agent-composition.md) | hybrid generated | `examples/coding-agent/cordis.yml` plugin list plus curated app/bundle expansion | -| [acp-agent app composition](../../../acp-agent-composition.md) | hybrid generated | `examples/acp-agent/cordis.yml` plugin list plus curated app/bundle expansion | +| [echo-agent app composition](../../../../examples/echo-agent/composition.md) | hybrid generated | `examples/echo-agent/cordis.yml` plugin list plus curated app/bundle expansion | +| [coding-agent app composition](../../../../examples/coding-agent/composition.md) | hybrid generated | `examples/coding-agent/cordis.yml` plugin list plus curated app/bundle expansion | +| [acp-agent app composition](../../../../examples/acp-agent/composition.md) | hybrid generated | `examples/acp-agent/cordis.yml` plugin list plus curated app/bundle expansion | | [event producer/consumer matrix](../../../event-producer-consumer.md) | hybrid generated | Cordis event declarations, AST-scanned `ctx.on/emit/parallel/serial/waterfall` sites, and explicit dynamic dispatch overrides | | [agent turn and step lifecycle](../../../agent-lifecycle.md) | curated | architecture.md loop lifecycle, Cordis catalog links, and session event semantics | | [tool execution pipeline](../../../tool-execution-pipeline.md) | curated | tool pipeline semantics and the `tools/execute` waterfall | diff --git a/docs/tool-execution-pipeline.md b/docs/tool-execution-pipeline.md index 81bac5eaaf..db50a3beec 100644 --- a/docs/tool-execution-pipeline.md +++ b/docs/tool-execution-pipeline.md @@ -3,8 +3,6 @@ # Tool Execution Pipeline -Maintenance mode: curated Mermaid flow; exact tool schemas and event signatures live in generated catalogs. - This graph shows where policy, hooks, sandboxing, filesystem guards, result rewriting, and UI rendering fit without changing the loop. The key extension points are the `tools/pre-execute` and `tools/post-execute` waterfalls. ```mermaid @@ -37,3 +35,5 @@ flowchart TD ``` Filesystem read-before-edit checks live below `tool-fs` on the `fs/*` event gate, while hook bridges and future permission prompts live on the generic tool waterfalls. That split lets the same hooks observe bash, fs, web, todo, and subagent calls without coupling those tools to one policy service. + +Maintenance mode: curated Mermaid flow; exact tool schemas and event signatures live in generated catalogs. diff --git a/docs/acp-agent-composition.md b/examples/acp-agent/composition.md similarity index 97% rename from docs/acp-agent-composition.md rename to examples/acp-agent/composition.md index db3a59eaed..2644a2b112 100644 --- a/docs/acp-agent-composition.md +++ b/examples/acp-agent/composition.md @@ -3,8 +3,6 @@ # ACP Agent App Composition -Maintenance mode: hybrid: the leaf plugin list is parsed from its `cordis.yml`; app package expansion is curated from package source. - The ACP demo exposes the same agent spine over JSON-RPC stdio, with no stdout logger and no pre-created agent; clients create sessions through the ACP bridge. ```mermaid @@ -64,4 +62,6 @@ flowchart LR | `hooks-claude` | `@deepseek-ai/dsh-hooks-claude` | | `hooks-codex` | `@deepseek-ai/dsh-hooks-codex` | -Source config: [`examples/acp-agent/cordis.yml`](../examples/acp-agent/cordis.yml). +Source config: [`examples/acp-agent/cordis.yml`](cordis.yml). + +Maintenance mode: hybrid: the leaf plugin list is parsed from its `cordis.yml`; app package expansion is curated from package source. diff --git a/docs/coding-agent-composition.md b/examples/coding-agent/composition.md similarity index 97% rename from docs/coding-agent-composition.md rename to examples/coding-agent/composition.md index 9a339499f8..926cb56109 100644 --- a/docs/coding-agent-composition.md +++ b/examples/coding-agent/composition.md @@ -3,8 +3,6 @@ # Coding Agent App Composition -Maintenance mode: hybrid: the leaf plugin list is parsed from its `cordis.yml`; app package expansion is curated from package source. - The coding REPL demo adds the real DeepSeek adapter, filesystem tools, todo_write, compaction, and both subagent transports on top of the stdio app package. ```mermaid @@ -64,4 +62,6 @@ flowchart LR | `fs-policy` | `@deepseek-ai/dsh-fs-policy` | | `tool-fs` | `@deepseek-ai/dsh-tool-fs` | -Source config: [`examples/coding-agent/cordis.yml`](../examples/coding-agent/cordis.yml). +Source config: [`examples/coding-agent/cordis.yml`](cordis.yml). + +Maintenance mode: hybrid: the leaf plugin list is parsed from its `cordis.yml`; app package expansion is curated from package source. diff --git a/docs/echo-agent-composition.md b/examples/echo-agent/composition.md similarity index 95% rename from docs/echo-agent-composition.md rename to examples/echo-agent/composition.md index 1e1e2719cf..1491c56955 100644 --- a/docs/echo-agent-composition.md +++ b/examples/echo-agent/composition.md @@ -3,8 +3,6 @@ # Echo Agent App Composition -Maintenance mode: hybrid: the leaf plugin list is parsed from its `cordis.yml`; app package expansion is curated from package source. - The echo demo swaps in a local mock LLM and teaching echo tool, then loads the stdio app package for the shared spine and terminal front door. ```mermaid @@ -37,4 +35,6 @@ flowchart LR | `bash` | `@deepseek-ai/dsh-bash-local` | | `stdio-agent` | `@deepseek-ai/dsh-stdio-agent` | -Source config: [`examples/echo-agent/cordis.yml`](../examples/echo-agent/cordis.yml). +Source config: [`examples/echo-agent/cordis.yml`](cordis.yml). + +Maintenance mode: hybrid: the leaf plugin list is parsed from its `cordis.yml`; app package expansion is curated from package source. diff --git a/scripts/gen-doc-graphs.ts b/scripts/gen-doc-graphs.ts index 931e01f041..183f8ca62a 100644 --- a/scripts/gen-doc-graphs.ts +++ b/scripts/gen-doc-graphs.ts @@ -5,7 +5,7 @@ * - module-graph.md answers "which packages depend on which packages?" * - cordis-catalog/ answers "which events and services exist?" * - tool-catalog/ answers "which tools does the model see?" - * - docs/*.md relationship diagrams answer "how do those pieces fit together?" + * - generated relationship diagrams answer "how do those pieces fit together?" * * Generated pages discover the enumerable facts from source. Hybrid pages use * discovered inventory plus small manifests for policy that source cannot infer @@ -19,7 +19,7 @@ */ import { existsSync, globSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs' -import { dirname, resolve } from 'node:path' +import { dirname, relative, resolve } from 'node:path' import ts from 'typescript' import { collectEvents, collectServices } from './gen-cordis-catalog.ts' @@ -196,18 +196,28 @@ const DYNAMIC_EVENT_DISPATCHERS: Array<{ event: string; pkg: string; method: str { event: 'subagent/end', pkg: 'subagent', method: 'events.dispatch' }, ] -function generatedHeader(title: string, source: string): string[] { +function generatedHeader(title: string): string[] { return [ '', '', `# ${title}`, '', - `Maintenance mode: ${source}.`, - '', ] } +function maintenanceFooter(source: string): string[] { + return [`Maintenance mode: ${source}.`, ''] +} + +function graphIndexLink(rel: string): string { + return relative('docs', rel).replaceAll('\\', '/') +} + +function linkFromDoc(docRel: string, targetRel: string): string { + return relative(dirname(docRel), targetRel).replaceAll('\\', '/') +} + function collectPackages(): Pkg[] { const pkgs: Pkg[] = [] for (const rel of globSync('packages/*/*/package.json', { cwd: root }).sort()) { @@ -305,6 +315,7 @@ function assertServiceRolesComplete(): void { function renderCapabilitySeams(pkgs: Pkg[]): string { assertServiceRolesComplete() const pkgsByShort = new Map(pkgs.map(pkg => [pkg.short, pkg])) + const maintenance = 'hybrid: services are discovered from Cordis declarations; interface/implementation/consumer roles are classified in `scripts/gen-doc-graphs.ts` with a completeness guard' const nodes = new Map() const edges = new Set() const companionEdges = new Set() @@ -312,7 +323,7 @@ function renderCapabilitySeams(pkgs: Pkg[]): string { if (!nodes.has(id)) nodes.set(id, ` ${id}["${escLabel(label)}"]`) } const addEdge = (from: string, to: string): void => { edges.add(` ${from} --> ${to}`) } - const lines = generatedHeader('Capability Seams And Core Services', 'hybrid: services are discovered from Cordis declarations; interface/implementation/consumer roles are classified in `scripts/gen-doc-graphs.ts` with a completeness guard') + const lines = generatedHeader('Capability Seams And Core Services') lines.push( 'A service can be a core spine service, a swappable capability seam, or a bundle/composition point. The graph shows the package that owns the service declaration, known implementation packages, and packages that consume the service directly.', '', @@ -343,7 +354,7 @@ function renderCapabilitySeams(pkgs: Pkg[]): string { for (const role of SERVICE_ROLES) { lines.push(`| \`ctx.${role.key}\` | \`${role.mode}\` | ${pkgLink(pkgsByShort.get(role.pkg), role.pkg)} | ${pkgList(role.implementations, pkgsByShort)} | ${pkgList(role.consumers, pkgsByShort)} | ${pkgList(role.companions, pkgsByShort)} | ${tableCell(role.note)} |`) } - lines.push('') + lines.push('', ...maintenanceFooter(maintenance)) return lines.join('\n') } @@ -375,7 +386,7 @@ function stripYamlScalar(value: string): string { const APP_EXAMPLES = [ { id: 'echo', - rel: 'docs/echo-agent-composition.md', + rel: 'examples/echo-agent/composition.md', title: 'Echo Agent App Composition', label: 'examples/echo-agent', config: 'examples/echo-agent/cordis.yml', @@ -383,7 +394,7 @@ const APP_EXAMPLES = [ }, { id: 'coding', - rel: 'docs/coding-agent-composition.md', + rel: 'examples/coding-agent/composition.md', title: 'Coding Agent App Composition', label: 'examples/coding-agent', config: 'examples/coding-agent/cordis.yml', @@ -391,7 +402,7 @@ const APP_EXAMPLES = [ }, { id: 'acp', - rel: 'docs/acp-agent-composition.md', + rel: 'examples/acp-agent/composition.md', title: 'ACP Agent App Composition', label: 'examples/acp-agent', config: 'examples/acp-agent/cordis.yml', @@ -421,7 +432,8 @@ function renderAppExpansion(lines: string[], appNode: string, pluginName: string function renderAppComposition(example: AppExample): string { const plugins = parseExampleCordis(example.config) - const lines = generatedHeader(example.title, 'hybrid: the leaf plugin list is parsed from its `cordis.yml`; app package expansion is curated from package source') + const maintenance = 'hybrid: the leaf plugin list is parsed from its `cordis.yml`; app package expansion is curated from package source' + const lines = generatedHeader(example.title) lines.push( example.summary, '', @@ -444,9 +456,9 @@ function renderAppComposition(example: AppExample): string { '| --- | --- |', ...plugins.map(plugin => `| \`${plugin.id}\` | \`${plugin.name}\` |`), '', - `Source config: ${repoLink(example.config, `\`${example.config}\``, '..')}.`, - '', + `Source config: [\`${example.config}\`](${linkFromDoc(example.rel, example.config)}).`, ) + lines.push('', ...maintenanceFooter(maintenance)) return lines.join('\n') } @@ -528,7 +540,8 @@ function renderEventRelations(pkgs: Pkg[]): string { const events = collectEvents() const relations = collectEventRelations() const pkgsByShort = new Map(pkgs.map(pkg => [pkg.short, pkg])) - const lines = generatedHeader('Event Producer And Consumer Matrix', 'hybrid generated: Cordis event declarations and most producer/listener edges are AST-scanned; dynamic dispatch sites are classified in `scripts/gen-doc-graphs.ts`') + const maintenance = 'hybrid generated: Cordis event declarations and most producer/listener edges are AST-scanned; dynamic dispatch sites are classified in `scripts/gen-doc-graphs.ts`' + const lines = generatedHeader('Event Producer And Consumer Matrix') lines.push( 'This matrix shows which packages dispatch each harness-owned event and which packages listen to it. It is intentionally a table rather than one large graph: events are many-to-many, and dense relation data is easier to review in rows. Dynamic dispatch overrides cover sites that deliberately bypass `ctx.emit`, such as subagent lifecycle containment.', '', @@ -549,13 +562,14 @@ function renderEventRelations(pkgs: Pkg[]): string { lines.push(`| \`${event}\` | ${relationPackages(relation.dispatchers, pkgsByShort)} | ${listenerPackages(relation.listeners, pkgsByShort)} |`) } } - lines.push('') + lines.push('', ...maintenanceFooter(maintenance)) return lines.join('\n') } function renderLifecycle(): string { + const maintenance = 'curated Mermaid sequence; exact event signatures live in the generated Cordis catalog' return [ - ...generatedHeader('Agent Turn And Step Lifecycle', 'curated Mermaid sequence; exact event signatures live in the generated Cordis catalog'), + ...generatedHeader('Agent Turn And Step Lifecycle'), 'This sequence is the visual companion to [architecture.md](architecture.md#loop-lifecycle-session--turn--step). It keeps durable replay facts on `session/event` and live control/status on `agent/*`.', '', '```mermaid', @@ -599,12 +613,14 @@ function renderLifecycle(): string { '', 'SDK users that need replayable transcript data should consume `session/event`; `agent/*` is the live coordination surface for queue/status, prompt interception, request shaping, steering, continuation, and errors.', '', + ...maintenanceFooter(maintenance), ].join('\n') } function renderToolPipeline(): string { + const maintenance = 'curated Mermaid flow; exact tool schemas and event signatures live in generated catalogs' return [ - ...generatedHeader('Tool Execution Pipeline', 'curated Mermaid flow; exact tool schemas and event signatures live in generated catalogs'), + ...generatedHeader('Tool Execution Pipeline'), 'This graph shows where policy, hooks, sandboxing, filesystem guards, result rewriting, and UI rendering fit without changing the loop. The key extension points are the `tools/pre-execute` and `tools/post-execute` waterfalls.', '', '```mermaid', @@ -638,12 +654,14 @@ function renderToolPipeline(): string { '', 'Filesystem read-before-edit checks live below `tool-fs` on the `fs/*` event gate, while hook bridges and future permission prompts live on the generic tool waterfalls. That split lets the same hooks observe bash, fs, web, todo, and subagent calls without coupling those tools to one policy service.', '', + ...maintenanceFooter(maintenance), ].join('\n') } function renderSnapshotReplay(): string { + const maintenance = 'curated Mermaid sequence based on the snapshot test harness' return [ - ...generatedHeader('ACP Snapshot Replay', 'curated Mermaid sequence based on the snapshot test harness'), + ...generatedHeader('ACP Snapshot Replay'), 'This graph explains what a snapshot scenario proves: recorded real-model session logs are replayed keylessly, ACP stdout is normalized and diffed, and scenario workspaces preserve tool side effects that the UI stream alone cannot prove.', '', '```mermaid', @@ -665,6 +683,7 @@ function renderSnapshotReplay(): string { '', 'The fs and hook snapshot matrix is valuable because it proves world state, hook decisions, and failed tool-card rendering, not just that replay returns text.', '', + ...maintenanceFooter(maintenance), ].join('\n') } @@ -685,9 +704,9 @@ function renderDocs(): GraphDoc[] { function renderIndex(docs: GraphDoc[]): string { const labels: Record = { 'docs/capability-seams.md': 'capability seams and core services', - 'docs/echo-agent-composition.md': 'echo-agent app composition', - 'docs/coding-agent-composition.md': 'coding-agent app composition', - 'docs/acp-agent-composition.md': 'acp-agent app composition', + 'examples/echo-agent/composition.md': 'echo-agent app composition', + 'examples/coding-agent/composition.md': 'coding-agent app composition', + 'examples/acp-agent/composition.md': 'acp-agent app composition', 'docs/event-producer-consumer.md': 'event producer/consumer matrix', 'docs/agent-lifecycle.md': 'agent turn and step lifecycle', 'docs/tool-execution-pipeline.md': 'tool execution pipeline', @@ -695,9 +714,9 @@ function renderIndex(docs: GraphDoc[]): string { } const modes: Record = { 'docs/capability-seams.md': 'hybrid generated', - 'docs/echo-agent-composition.md': 'hybrid generated', - 'docs/coding-agent-composition.md': 'hybrid generated', - 'docs/acp-agent-composition.md': 'hybrid generated', + 'examples/echo-agent/composition.md': 'hybrid generated', + 'examples/coding-agent/composition.md': 'hybrid generated', + 'examples/acp-agent/composition.md': 'hybrid generated', 'docs/event-producer-consumer.md': 'hybrid generated', 'docs/agent-lifecycle.md': 'curated', 'docs/tool-execution-pipeline.md': 'curated', @@ -707,12 +726,13 @@ function renderIndex(docs: GraphDoc[]): string { '| [module dependency graph](module-graph.md) | `generated` |', '| [tool schema catalog and package map](tool-catalog/tools.md) | `generated` |', ...docs.map((doc) => { - const link = doc.rel.replace(/^docs\//, '') + const link = graphIndexLink(doc.rel) return `| [${labels[doc.rel] ?? link}](${link}) | \`${modes[doc.rel] ?? 'generated'}\` |` }), ] + const maintenance = 'mixed: each linked page declares generated, hybrid, or curated mode' return [ - ...generatedHeader('Documentation Graph Index', 'mixed: each linked page declares generated, hybrid, or curated mode'), + ...generatedHeader('Documentation Graph Index'), 'These diagrams are the relationship layer above the generated catalogs. Use them to navigate package topology, capability seams, event flow, model-facing tools, app composition, and runtime lifecycle paths. Exact signatures and type shapes still live in the generated [events](cordis-catalog/events.md) / [services](cordis-catalog/services.md) catalogs, [tool-catalog/](tool-catalog/tools.md), and [core-data-structures/](core-data-structures/core.md).', '', 'The process decision behind this index is recorded in [the documentation graph RFC](rfc/implemented/process/2026-07-03-documentation-graph-atlas.md).', @@ -723,6 +743,7 @@ function renderIndex(docs: GraphDoc[]): string { '', 'Regenerate with `pnpm run gen-doc-graphs`; verify freshness with `pnpm run verify-doc-graphs`.', '', + ...maintenanceFooter(maintenance), ].join('\n') } From 96055d1763d6ef954d1d6f61a76c7790ae6702e5 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 5 Jul 2026 03:32:11 +0800 Subject: [PATCH 10/14] docs: rewrite architecture intro --- docs/architecture.md | 202 ++++++++++++++---------------- scripts/doc-budgets.manifest.json | 2 +- 2 files changed, 93 insertions(+), 111 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index 52e5bc4439..5b6f6c1823 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,147 +1,129 @@ # DeepSeek Harness Architecture -This document describes the architecture of the DeepSeek Harness — the foundation of **DeepSeek Code**. The governing principle: **everything is a plugin**. The core is deliberately tiny — a handful of abstract services plus one concrete loop plugin (`dsh-agent-loop`) — and every product feature is a plugin against the extension surface described here, without modifying the loop. The stack is three tiers: plugins (the loop itself, seam implementations, model-facing tools, bridges) over interface/service packages (each owning one `ctx` key and its vocabulary) over the vendored Cordis kernel (`vendor/`). +DeepSeek Harness is the plugin runtime behind **DeepSeek Code**. The governing idea is small and strict: the harness core owns the vocabulary and the turn driver, and every product capability is a Cordis plugin attached through a typed service or event seam. Model adapters, tools, persistence, filesystem access, hooks, UI bridges, compaction, and subagents all enter the system the same way. -This document covers **behavior**; type shapes live in [core-data-structures/](core-data-structures/core.md), the per-event/service reference in the generated [events](cordis-catalog/events.md) / [services](cordis-catalog/services.md) catalogs, visual relationship maps in the [documentation graph index](graph-atlas.md), and per-package contracts in the package READMEs ([map](../packages/README.md)). +Read this page as the system map before changing `packages/`. It describes behavior: what services exist, how a turn moves through the loop, where plugins extend it, and which invariants keep replay and hot reload sane. Literal type shapes live in [core-data-structures/](core-data-structures/core.md), exact event and service signatures live in the generated [events](cordis-catalog/events.md) and [services](cordis-catalog/services.md) catalogs, visual relationship maps live in the [documentation graph index](graph-atlas.md), and package-level contracts live in the package READMEs from the [package map](../packages/README.md). -## Service map +## The Mental Model -The spine — the product API under `packages/core/`: +A running harness is one Cordis context. Packages contribute three kinds of things to it: + +- **Services** on `ctx.`: stable call surfaces such as `ctx.llm`, `ctx.tools`, or `ctx.sessions`. +- **Events**: interception and notification seams such as `agent/request`, `tools/pre-execute`, or `session/event`. +- **Registrations**: prompt sections, tool schemas, adapters, providers, and listeners, all installed through `ctx.effect()`, `ctx.on()`, or `ctx.waterfall()` so disposal unwinds them. + +The default agent loop is intentionally ordinary: drain queued work, assemble a request, stream a model answer, run tools, decide whether to continue, flush durable state. The important part is where the loop pauses. Every pause is a seam a plugin can program against without reaching into loop internals. + +That rule is the design pressure behind the repo layout. Interface packages own vocabulary and `ctx` keys; implementation packages register concrete backends; consumer packages expose model-facing tools or app-facing bridges. A product feature should usually be a plugin on an existing seam, not a patch to `dsh-agent-loop`. + +## Service Map + +The product spine in [`packages/core/`](../packages/core/README.md) is the minimum language of an agent run: | ctx key | Package | Role | |---|---|---| -| `ctx.sessions` | dsh-session | creates/holds event-sourced `Session`s | -| `ctx.systemPrompt` | dsh-system-prompt | ordered sections + tool schemas → `assemble()` | -| `ctx.tools` | dsh-tools | tool definitions; `execute()` through waterfall | -| `ctx.agents` | dsh-agent | live `Agent` handles + create/resume factory (returns `AgentHandle { agent, dispose() }`) | -| `ctx.agentLoop` | dsh-agent-loop | THE concrete loop plugin: creates and drives `ReactLoopAgent`s | +| `ctx.sessions` | `dsh-session` | in-memory event-sourced sessions | +| `ctx.systemPrompt` | `dsh-system-prompt` | ordered prompt sections plus tool schemas | +| `ctx.tools` | `dsh-tools` | tool registry and execution pipeline | +| `ctx.agents` | `dsh-agent` | live agent registry, public `Agent` handle, `agent/*` vocabulary | +| `ctx.agentLoop` | `dsh-agent-loop` | the concrete `ReactLoopAgent` driver | -The swappable capability seams: +The swappable seams sit around that spine: -| ctx key | Package | Role | +| ctx key | Package family | Role | |---|---|---| -| `ctx.llm` | dsh-llm | adapter registry; `stream()` | -| `ctx.sessionPersistence` | dsh-session-persistence | durable persistence: create/append/load/list | -| `ctx.bash` | dsh-bash | bash execution: foreground runs + background tasks | -| `ctx.fs` | dsh-fs | filesystem provider: read/stream, atomic writes/edits; owns the `fs/*` policy events | -| `ctx.compact` | dsh-compact | compaction: detect pressure, summarize an older range | -| `ctx.web` | dsh-web | search/fetch provider registries + `WebError` taxonomy | -| `ctx.subagents` | dsh-subagent | named provider registry for delegating to child agents | +| `ctx.llm` | [`llm/`](../packages/llm/README.md) | adapter registry and streaming model calls | +| `ctx.bash` | [`bash/`](../packages/bash/README.md) | foreground/background command execution | +| `ctx.fs` | [`fs/`](../packages/fs/README.md) | filesystem provider primitives; `fs/*` policy events | +| `ctx.web` | [`web/`](../packages/web/README.md) | search/fetch provider registries | +| `ctx.compact` | [`compact/`](../packages/compact/README.md) | session-surface compaction | +| `ctx.subagents` | [`subagent/`](../packages/subagent/README.md) | named delegation providers | +| `ctx.sessionPersistence` | [`session-persistence/`](../packages/session-persistence/README.md) | durable storage for session logs | -Dependency rule: plugins depend on these interfaces, never on `dsh-agent-loop` — the loop is swappable; the sanctioned exception is the composition bundle `dsh-agent-core`, whose job is assembling the concrete spine ([full rule + generated graph](../packages/README.md#dependencies)). +`dsh-agent-core` is the sanctioned composition exception to the dependency rule: it depends on the concrete loop because its job is to assemble the default providerless spine. Extension plugins depend on interfaces and event vocabulary, never on `dsh-agent-loop`; swapping the loop means shipping a different bundle, not rewiring every extension. -All registrations go through `ctx.effect()` and return disposers, so hot-reload and fiber disposal clean up automatically (full service interfaces: the generated [services catalog](cordis-catalog/services.md)). +## Capability Seams -## Cordis waterfall semantics +The default seam shape is **interface / implementation / consumer**. The interface package owns the `ctx` key, abstract service, event vocabulary, and shared types. An implementation package registers one concrete backend. A consumer package, often a `tool-*` package, depends only on the interface and registers model-facing behavior through `ctx.tools` or prompt assembly. The bash family is the reference shape: `dsh-bash`, `dsh-bash-local`, `dsh-tool-bash`. -`ctx.waterfall` is **around-middleware**, not a value reducer. Each listener receives `(...args, next)`: +Several seams intentionally bend that template. The LLM seam keeps interface and consumer vocabulary together because adapters are the only implementations. The filesystem family adds `dsh-fs-policy` as an event-gate plugin: `dsh-tool-fs` dispatches `fs/write-intent`, `fs/edit-intent`, and `fs/observed`, while the policy listens without becoming a method service the tool must inject. The web seam is one service with search and fetch provider registries, so provider swaps do not rename model tools. The subagent seam is a named provider registry because multiple delegation backends can coexist in one context. -- call `next()` to delegate to later listeners (and ultimately the core behavior), possibly wrapping it; -- return a value **without** calling `next()` to short-circuit (veto); -- listeners run in registration order; `prepend: true` jumps the queue. +## Cordis Waterfall Semantics -Composition caveat: values propagate through `next()`'s **return value** — a listener that returns a *new* object makes earlier listeners' mutations invisible downstream. Prefer mutate-then-`next()` for cooperative middleware; return a replacement only to take over the result. +`ctx.waterfall` is around-middleware, not a reducer. A listener receives `(...args, next)` and chooses one of three behaviors: -## Capability seams: interface / implementation / consumer +- call `next()` to delegate to later listeners and the core behavior, optionally wrapping the result; +- return without calling `next()` to short-circuit with its own result; +- register with `prepend: true` when it must run before existing listeners. -Swappable capabilities split into three packages — **interface** (abstract service + vocabulary, owns the ctx key), **implementation** (a concrete subclass loaded as a plugin), **consumer** (what the model and plugins program against) — so each evolves independently; the bash trio is the template ([capability seams RFC](rfc/implemented/architecture/2026-06-13-capability-seams.md)). Keep interface + consumer together when they are one concern (the LLM seam: `dsh-llm` carries both, adapters implement); don't split preemptively. +Values propagate through `next()`'s return value. Cooperative listeners mutate a shared object and then delegate; replacing an object is a takeover, because earlier mutations on the old object will not be seen downstream. For single-slot decision events such as `fs/write-intent`, returning without `next()` is the point: the first decider owns the decision. -Two seams bend the template deliberately: +## Sessions And Messages -- **Filesystem** adds a policy layer as an **event gate**, not a method service: `dsh-tool-fs` (the `read`/`write`/`edit` tools AND executor) dispatches `fs/*` intent events that `dsh-fs-policy` decides, so dropping the policy plugin degrades to the bare provider instead of breaking an injection ([event-gate RFC](rfc/implemented/architecture/2026-06-26-file-context-as-event-gate.md)). Paths resolve against the caller's session cwd, matching bash ([per-session cwd RFC](rfc/implemented/architecture/2026-07-02-fs-per-session-cwd.md)). -- **Web** folds search and fetch onto one seam: `ctx.web` is a provider REGISTRY (`registerSearchProvider`/`registerFetchProvider`, registration-order-independent selection); providers register like LLM adapters, and `dsh-tool-web` is the single consumer owning the tool schemas ([web seam RFC](rfc/implemented/architecture/2026-06-24-web-capability-seam.md)). +A `Session` is an append-only log of typed `SessionEvent`s. The log is the source of truth for replay, UI rendering, persistence, and derived model history. `deriveMessages()` projects surface events into the `Message[]` sent to the model; raw `assistant/chunk` entries stay in the log for replay and transcript fidelity but do not become prompt history. Persistence backends subscribe to `session/event`, buffer snapshots of appended events, and drain them at the awaited `session/flush` checkpoint. -> The seam pattern is plain Cordis services + `inject` (a consumer's fiber stays pending until the service exists). Despite the name, `@cordisjs/plugin-capability` is unrelated — a permission-security service (a candidate for the deferred permissions work), not a mechanism for swapping implementations. +Messages are arrays of typed content blocks from `dsh-llm`: `text`, `reasoning`, `tool-call`, and `tool-result`. The block union, message sources, finish reasons, turn triggers, turn-end reasons, and session event variants use the merge-extensible-map pattern documented in [core-data-structures](core-data-structures/core.md). A plugin can extend a map, but every shipping path that observes the new variant must be taught what it means. -## Content blocks and streaming (dsh-llm) +## Loop Lifecycle -Messages are arrays of typed **content blocks** (`text`, `reasoning`, `tool-call`, `tool-result`); the union derives from the merge-extensible `ContentBlockMap`; the same pattern types `MessageSource`, `FinishReason`, `TurnTrigger`, `TurnEndReason`. The core set is limited to blocks every shipping path honors — multimodal content (images, audio, …) has no core block type; a feature that needs one adds it via the map in the same coordinated change that maps it in the adapters, surfaces it in the UI bridges, and prices it in compaction ([the drop-image RFC](rfc/implemented/simplification/2026-07-04-drop-image-content-block.md)). Streaming is a raw chunk protocol (`block-start` … `finish`) with `BlockAssembler` as the single shared chunk→block assembler; the loop logs raw chunks (replay fidelity) while assembling them. `LlmAdapter` is the provider seam: subclass, implement `stream()`, register via `ctx.llm.registerAdapter(models, adapter)`; `dsh-llm-deepseek` and `dsh-llm-pi-ai` implement the one contract as deliberate design twins ([twin RFC](rfc/implemented/architecture/2026-06-13-twin-llm-adapters.md)). The StreamChunk conventions (usage/finish ordering, raw-string tool arguments, the two sanctioned error paths) are pinned in `dsh-llm/src/types.ts` and [llm-streaming.md](core-data-structures/llm-streaming.md). +The loop uses three nested units: -## Event-sourced sessions (dsh-session) +- **Session**: the full append-only event log for one agent. +- **Turn**: one drained batch of queued work, running until the model stops asking for tools and no plugin requests continuation. +- **Step**: one model request plus the tool executions caused by that response. -A `Session` is an append-only log of typed `SessionEvent`s — the single source of truth. The LLM message history is *derived* (`deriveMessages()`): user/assistant messages, tool results, and envelope-tagged context/steering messages come from their events in chronological order (raw `assistant/chunk` events are replay/UI data, skipped; the per-event mapping is in [session.md](core-data-structures/session.md)). Replay/fork = `ctx.sessions.create(id, { seed })`; trace/telemetry = listen to `session/event` ([event-sourcing RFC](rfc/implemented/architecture/2026-06-11-event-sourced-sessions.md)). +One turn follows this shape: -**Durability**: `session/event` is a synchronous notification; persistence backends buffer write-behind and drain at the awaited `session/flush` checkpoint at every turn end. The abstract `SessionPersistence` seam defines create/append/load/list over `SessionEvent` (no parallel persisted type); metadata travels as `SessionHeader`; crash recovery preserves an interrupted turn by closing it with a synthetic `turn/end {interrupted}`. Two backends (JSONL, SQLite) pass one shared contract suite ([persistence RFC](rfc/implemented/architecture/2026-06-14-session-persistence.md), [write coordinator RFC](rfc/implemented/architecture/2026-06-18-shared-persistence-write-coordinator.md)). Resume = `ctx.agents.resume({ resumeSessionId })`. - -## Prompt assembly (dsh-system-prompt) - -Plugins contribute `PromptSection`s (named, ordered, static or computed) and tool-schema providers; `assemble()` returns `PromptAssembly { sections, tools }` through the `system-prompt/assemble` waterfall. Tool schemas are deliberately part of the assembly — "what the model is told it can do" is one coherent thing — though adapters transmit them as the wire-level `tools` field ([RFC](rfc/implemented/architecture/2026-06-11-tool-schemas-in-prompt-assembly.md)). - -## Tool pipeline (dsh-tools) - -`ToolRegistry.register()` takes schema + `execute()`; schemas flow into the assembly automatically. `execute()` runs through a two-waterfall pipeline — `tools/pre-execute` (a `PreToolDecision`: allow/deny/ask) → core dispatch → `tools/post-execute` (a `PostToolDecision`: accept/block, replace content, attach context) — the seams where sandbox, permission, hook, and plan-mode plugins live. A thrown tool still reaches `post-execute` as an `isError` result. - -## Agents (dsh-agent) and the loop (dsh-agent-loop) - -`Agent` is the handle every plugin programs against: `send()` (queued), `steer()` (mid-turn injection, drained between steps), `inject()` (in-session context; a one-shot `injection` turn when idle), `cancel()` (the single public stop primitive: clears queued + steering work, aborts the in-flight step, drops a turn about to start), `whenIdle()` (quiescence observation, not teardown), plus `session`/`status`/`options`. A lifecycle owner tears down via `await AgentHandle.dispose()` — stop, await exit, unregister. Full semantics: [core.md](core-data-structures/core.md), [lifecycle RFC](rfc/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-seams.md). - -**Subagents** are a seam, not a method on `Agent`: `ctx.subagents` is a named-provider registry (`spawn` starts fresh, `fork` seeds the child with the parent's completed-turn prefix, ACP drives an out-of-process child); children are ordinary `Agent`s. See [subagent.md](core-data-structures/subagent.md), [subagent RFC](rfc/implemented/feature/2026-06-21-subagent-capability-seam.md). - -### Loop lifecycle (session / turn / step) - -- **Session**: the whole event log of one agent. -- **Turn**: ≥1 queued message; steps run until the model stops requesting tools and no plugin requests continuation. -- **Step**: one model request + its tool executions. - -``` -create agent → emit agent/session-start(source) ⟵ once, before turn 1 (startup|resume) -forever: - wait for queued messages (idle) - emit agent/status(running) - TURN (error-contained — a throwing plugin ends the turn, never the loop): - 'turn/start' ⟵ durable turn boundary (no agent/* mirror) - each queued msg: waterfall agent/prompt-submit ⟵ allow (rewrite/+context) | block - allow → session('user/message'…); inject additionalContext - every prompt blocked → 'turn/end'(rejected), 0 steps ⟵ zero-step turn, model never called - STEP loop: - drain steering (late steering from previous step's listeners) - assembly = ctx.systemPrompt.assemble() ⟵ waterfall system-prompt/assemble - await ctx.serial('agent/pre-step') ⟵ surface mutation (compaction) OUTSIDE the step - session('step/start') ⟵ durable step boundary (no agent/* mirror) - req = {model, system, tools, messages: session.deriveMessages(), signal} - req = waterfall agent/request ⟵ hooks, model switch - stream ctx.llm.stream(req) ⟵ waterfall llm/stream (raw chunks) - session('assistant/chunk') - if assembler.finish is error/aborted: throw ⟵ adapter's in-band error path → - step error (turn ends error/aborted, - not a normal completed message) - msg = waterfall agent/step-result ⟵ runs BEFORE the log append, so the - session('assistant/message' {content, usage?}) log records what tool dispatch uses - each tool-call (sequential, abort-checked between calls): - session('tool/call'); ctx.tools.execute() ⟵ waterfall tools/pre-execute (allow/ - deny/ask gate) → dispatch → tools/post-execute (accept/block, replace, +context) - tool execution may append tool-owned session events, e.g. `todo/write` - session('tool/result') - append buffered post-execute additionalContext → session('context/message')(s) - ⟵ after ALL tool/results (adjacency) - drain steering → session('steering/message') - session('step/end') ⟵ durable step boundary (no agent/* mirror) - cont = waterfall agent/turn-continuation(default = {action: hadToolCalls||steered - ? 'continue' : 'stop'}) → ContinuationDecision - a continue's reason is recorded as next-step steering (same turn); steering pending - also forces continue (continuation OR step/end listeners — the /goal pattern) - if action==stop: break - session('turn/end') ⟵ durable turn boundary (no agent/* mirror) - await ctx.parallel('session/flush', session) ⟵ durability checkpoint (failure - reported via agent/error, not fatal) - leftover steering re-enqueued as queued messages ⟵ steering is never stranded - emit agent/status(idle) unless more queued +```text +agent/session-start once per live agent +turn/start durable boundary + agent/prompt-submit allow, rewrite, attach context, or block each queued prompt + system-prompt/assemble sections + tool schemas + agent/pre-step surface mutation before history derivation, e.g. compaction + step/start durable boundary + agent/request mutate the GenerateOptions before the model call + llm/stream stream raw chunks from the selected adapter + assistant/chunk* replay/UI facts + agent/step-result inspect or rewrite the assembled assistant message + assistant/message the message used for tool dispatch and future history + tool/call -> ctx.tools.execute -> tool/result + tools/pre-execute allow, deny, or ask before dispatch + tools/post-execute accept, block, replace output, or attach context + context/message* buffered post-tool context, after all tool results + steering/message* mid-turn steering for the next step + step/end durable boundary + agent/turn-continuation continue or stop +turn/end durable boundary +session/flush awaited durability checkpoint ``` -Error containment: a throwing listener or broken step ends the **turn** (`turn/end { reason: { kind: 'error', step, … } }`), never the driver loop; live diagnostics fire via `agent/error`; an adapter's in-band error/aborted finish chunk becomes a step error. `cancel()` is honored mid-stream and between tool calls; disposal mid-turn ends the turn `disposed`. A post-`turn/end` failure (a rejecting `session/flush`) is reported via `agent/error` only — the turn stays balanced, the backend keeps its buffer. +Tool calls are sequential, and the loop checks cancellation between calls. Post-tool context is appended after all tool results so the tool-call/result adjacency remains stable. Steering injected while a turn is running is drained between steps; leftover steering after a turn is re-queued so it is never stranded. -A turn ends with one `TurnEndReason` — `completed`, `aborted`, `error`, `disposed`, `max-tokens`, `rejected`, or `interrupted`; per-variant semantics (and the max-tokens-wins rule) are in [session.md § TurnEndReasonMap](core-data-structures/session.md#why-a-turn-ended-turnendreasonmap). +## Event Domains -**Turn-enclosure invariant**: every session event lives inside a turn, making the turn the single durability/replay boundary — anything after the last `turn/end` is an interrupted-crash tail. `dsh-invariants` enforces it in dev ([invariant RFC](rfc/implemented/architecture/2026-06-15-turn-enclosure-invariant.md)). +`session/*` events are durable, replayable facts. Anything a UI can reconstruct after reload, including transcript surface, todo state, hook provenance, compaction records, and crash recovery markers, belongs on the session log or a merge-extensible session event. -## Event taxonomy +`agent/*` events are the live runtime surface. They carry an `Agent` object and power hooks, status observers, request mutation, prompt gating, step-result validation, and continuation policy. The declarations live in `dsh-agent`, not in `dsh-agent-loop`, so plugins can depend on the public agent vocabulary without depending on the concrete loop. -The `agent/*` events are declared in `dsh-agent` (so nothing depends on the loop package); each other service declares its own (`tools/*`, `llm/*`, `system-prompt/*`, `session/*`). The full catalog — signatures, dispatch modes, prose — is generated from source and freshness-gated: [cordis-catalog/events.md](cordis-catalog/events.md). Domain semantics (session = the fact log, agent = the live surface): [the event-domain RFC](rfc/implemented/architecture/2026-06-30-event-domain-semantics.md). +Capability events belong to the seam that owns their vocabulary: `tools/*` for tool execution, `llm/*` for model streaming, `system-prompt/*` for assembly, `fs/*` for filesystem policy, `subagent/*` for delegation runs, and `session/flush` for durability. The generated [events catalog](cordis-catalog/events.md) is the exhaustive reference and is freshness-gated. -## Extension guide +## Failure Boundaries -Plugin skeletons (tool, hook/permission gate, UI, protocol bridge) and the feature→mechanism map — which extension seam implements each product feature — live in [the extension cookbook](cookbook/extension-cookbook.md); step-by-step guides: [adding a package](cookbook/adding-a-package.md), [a tool](cookbook/adding-a-tool.md), [an LLM adapter](cookbook/adding-an-llm-adapter.md), [a vendored package](cookbook/adding-a-vendored-package.md). +The turn is the loop's containment boundary. A throwing listener, adapter error finish, or failed step ends the current turn with an error reason and reports live diagnostics through `agent/error`; it does not kill the driver loop. `cancel()` clears queued and steering work, aborts the active model/tool boundary when possible, and records the appropriate turn end. Disposal stops the loop, awaits quiescence, unregisters the agent, and lets service disposers drain their work. -## Deferred work (TODO) +Every session event is turn-enclosed. A backend that reloads a crashed session preserves the interrupted tail and closes it with a synthetic `interrupted` turn end rather than truncating real work. A failure after `turn/end`, such as a rejecting `session/flush`, is reported through `agent/error` only because there is no safe in-turn position left for a durable session event. -Designed-for but not implemented: inter-agent channels beyond delegation (shared state, streaming output); the model-facing `/compact` consumer tool over `ctx.compact` ([compaction RFC](rfc/implemented/feature/2026-06-18-compaction-capability-seam.md)); parallel tool execution (concurrency-safety hints on `ToolDefinition`); session branching/tree if seed-based forking proves insufficient. +## Extending The Harness + +Start from the extension point, not the loop: + +| Goal | Mechanism | +|---|---| +| Add a model provider | register an adapter on `ctx.llm` | +| Add a model-facing capability | register a tool on `ctx.tools`; schemas flow into prompt assembly | +| Add an executor or storage backend | implement the owning seam and register the service | +| Intercept prompts, requests, tool use, or continuation | listen on the relevant `agent/*` or `tools/*` waterfall | +| Add UI or editor integration | drive `ctx.agents` and render from `session/event` | +| Add durable session state | add a `SessionEventMap` member and render/replay from the log | + +The [extension cookbook](cookbook/extension-cookbook.md) maps common features to seams, and the step-by-step guides cover [packages](cookbook/adding-a-package.md), [tools](cookbook/adding-a-tool.md), [LLM adapters](cookbook/adding-an-llm-adapter.md), and [vendored packages](cookbook/adding-a-vendored-package.md). When a change seems to require editing `dsh-agent-loop`, first name the missing seam; if the loop really changes, update this map in the same PR. diff --git a/scripts/doc-budgets.manifest.json b/scripts/doc-budgets.manifest.json index 26db66b995..4a13f5a0fd 100644 --- a/scripts/doc-budgets.manifest.json +++ b/scripts/doc-budgets.manifest.json @@ -1,7 +1,7 @@ { "AGENTS.md": 1575, "docs/AGENTS.md": 1315, - "docs/architecture.md": 1890, + "docs/architecture.md": 1650, "docs/defensive-patterns.md": 550, "docs/testing.md": 800, "examples/AGENTS.md": 610, From dca7a436e4039250e685da15e2bebd545ea86b29 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 5 Jul 2026 15:31:50 +0800 Subject: [PATCH 11/14] docs: update product name --- AGENTS.md | 2 +- docs/architecture.md | 2 +- .../2026-06-21-mandatory-app-attribution-headers.md | 2 +- .../implemented/process/2026-06-11-vendor-cordis-as-source.md | 2 +- examples/acp-agent/README.md | 2 +- packages/core/system-prompt/tests/system-prompt.spec.ts | 4 ++-- packages/ui/acp/README.md | 2 +- packages/ui/acp/package.json | 2 +- packages/web/web-search-deepseek/tests/deepseek.e2e.ts | 2 +- packages/web/web-search-exa/tests/exa.e2e.ts | 2 +- packages/web/web-search-perplexity/tests/perplexity.e2e.ts | 2 +- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2832af8cda..9521dee491 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # AGENTS.md -This is the monorepo of the DeepSeek Harness group; it hosts **DeepSeek Code**, DeepSeek's coding agent product. The codebase is built on the vendored Cordis framework, microkernel-style: **everything is a plugin**. Read [docs/architecture.md](docs/architecture.md) before changing anything under `packages/` — the service map, event taxonomy, loop lifecycle, and extension seams. The documentation standard is [docs/AGENTS.md](docs/AGENTS.md). +This is the monorepo of the DeepSeek Harness group; it hosts **DeepSeek Harness SDK**, a plugin-based SDK for building agent harnesses. The codebase is built on the vendored Cordis framework, microkernel-style: **everything is a plugin**. Read [docs/architecture.md](docs/architecture.md) before changing anything under `packages/` — the service map, event taxonomy, loop lifecycle, and extension seams. The documentation standard is [docs/AGENTS.md](docs/AGENTS.md). ## Pre-release stance: foundation over blast radius diff --git a/docs/architecture.md b/docs/architecture.md index 5b6f6c1823..b0d42d8432 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,6 +1,6 @@ # DeepSeek Harness Architecture -DeepSeek Harness is the plugin runtime behind **DeepSeek Code**. The governing idea is small and strict: the harness core owns the vocabulary and the turn driver, and every product capability is a Cordis plugin attached through a typed service or event seam. Model adapters, tools, persistence, filesystem access, hooks, UI bridges, compaction, and subagents all enter the system the same way. +DeepSeek Harness SDK is the plugin runtime for building agent harnesses. The governing idea is small and strict: the harness core owns the vocabulary and the turn driver, and every product capability is a Cordis plugin attached through a typed service or event seam. Model adapters, tools, persistence, filesystem access, hooks, UI bridges, compaction, and subagents all enter the system the same way. Read this page as the system map before changing `packages/`. It describes behavior: what services exist, how a turn moves through the loop, where plugins extend it, and which invariants keep replay and hot reload sane. Literal type shapes live in [core-data-structures/](core-data-structures/core.md), exact event and service signatures live in the generated [events](cordis-catalog/events.md) and [services](cordis-catalog/services.md) catalogs, visual relationship maps live in the [documentation graph index](graph-atlas.md), and package-level contracts live in the package READMEs from the [package map](../packages/README.md). diff --git a/docs/rfc/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md b/docs/rfc/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md index 26ca40c647..5fbac8f663 100644 --- a/docs/rfc/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md +++ b/docs/rfc/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md @@ -67,7 +67,7 @@ Endpoint detection is not part of this RFC because no endpoint-specific mapping **Config-only opt-in attribution.** Rejected. A default-off setting is exactly how adapters keep drifting. The policy is mandatory default attribution with overrideable public values, not optional attribution. -**Product-named token (`deepseek-code`).** Considered for the `User-Agent` token, since the product's name is DeepSeek Code. `deepseek-harness` won on continuity: it is the identity providers already see from this codebase, it matches the org/repo and planned SDK-repo naming, and a public rename can change the product token deliberately later. +**Product-named token (`deepseek-harness-sdk`).** Considered for the `User-Agent` token, since the product name is DeepSeek Harness SDK. `deepseek-harness` won on continuity: it is the identity providers already see from this codebase, it matches the org/repo identity and package scope, and it keeps wire attribution stable while display copy carries the product name. ## Risks / what we give up diff --git a/docs/rfc/implemented/process/2026-06-11-vendor-cordis-as-source.md b/docs/rfc/implemented/process/2026-06-11-vendor-cordis-as-source.md index 8b2e29e17c..da457688af 100644 --- a/docs/rfc/implemented/process/2026-06-11-vendor-cordis-as-source.md +++ b/docs/rfc/implemented/process/2026-06-11-vendor-cordis-as-source.md @@ -6,7 +6,7 @@ Status: implemented (accepted 2026-06-11) ## Context -DeepSeek Code is built on the Cordis framework. Cordis core was at 4.0.0-rc.6 (a release candidate) when this repo started; the harness depends on framework internals (fiber lifecycle, effect disposal, waterfall dispatch) whose exact behavior matters to the agent loop's correctness guarantees. +DeepSeek Harness SDK is built on the Cordis framework. Cordis core was at 4.0.0-rc.6 (a release candidate) when this repo started; the harness depends on framework internals (fiber lifecycle, effect disposal, waterfall dispatch) whose exact behavior matters to the agent loop's correctness guarantees. ## Decision diff --git a/examples/acp-agent/README.md b/examples/acp-agent/README.md index 278ea74e53..58a5984301 100644 --- a/examples/acp-agent/README.md +++ b/examples/acp-agent/README.md @@ -1,6 +1,6 @@ # acp-agent example -The DeepSeek Harness agent demo exposed as an **Agent Client Protocol (ACP)** server over JSON-RPC stdio — drive it from Zed or any other ACP client. +The DeepSeek Harness SDK agent demo exposed as an **Agent Client Protocol (ACP)** server over JSON-RPC stdio — drive it from Zed or any other ACP client. ```sh pnpm run demo:acp # needs DEEPSEEK_API_KEY (repo-root .env or env) diff --git a/packages/core/system-prompt/tests/system-prompt.spec.ts b/packages/core/system-prompt/tests/system-prompt.spec.ts index cfbf36cbec..50a90d2e1c 100644 --- a/packages/core/system-prompt/tests/system-prompt.spec.ts +++ b/packages/core/system-prompt/tests/system-prompt.spec.ts @@ -7,7 +7,7 @@ describe('SystemPrompt', () => { const ctx = new Context() await ctx.plugin(SystemPrompt) - ctx.systemPrompt.section({ name: 'persona', order: 0, text: 'You are DeepSeek Code.' }) + ctx.systemPrompt.section({ name: 'persona', order: 0, text: 'You are DeepSeek Harness SDK.' }) ctx.systemPrompt.section({ name: 'cwd', order: 20, text: () => 'cwd: /tmp' }) ctx.systemPrompt.section({ name: 'rules', order: 10, text: 'Be precise.' }) ctx.systemPrompt.tools(() => [{ name: 'echo', description: 'echo back', parameters: {} }]) @@ -15,7 +15,7 @@ describe('SystemPrompt', () => { const assembly = await ctx.systemPrompt.assemble() expect(assembly.sections.map(s => s.name)).toEqual(['persona', 'rules', 'cwd']) expect(assembly.tools).toEqual([{ name: 'echo', description: 'echo back', parameters: {} }]) - expect(renderPrompt(assembly)).toBe('You are DeepSeek Code.\n\nBe precise.\n\ncwd: /tmp') + expect(renderPrompt(assembly)).toBe('You are DeepSeek Harness SDK.\n\nBe precise.\n\ncwd: /tmp') }) it('removes contributions when the contributing fiber is disposed (HMR safety)', async () => { diff --git a/packages/ui/acp/README.md b/packages/ui/acp/README.md index 38596b7fd0..b331432a7c 100644 --- a/packages/ui/acp/README.md +++ b/packages/ui/acp/README.md @@ -1,6 +1,6 @@ # @deepseek-ai/dsh-acp -The **Agent Client Protocol (ACP)** bridge: exposes the DeepSeek Harness coding agent as an ACP server over JSON-RPC stdio, so editors (Zed and other ACP clients) can drive it — streaming render, tool-call display, and resumable sessions. Zed is the current target client: baseline ACP behavior should remain reasonable for other clients, but bridge capabilities and compatibility decisions are evaluated against Zed first. **N concurrent sessions per connection** (see [ACP multi-session](../../../docs/rfc/proposed/feature/2026-06-14-acp-multi-session.md)): each maps to its own `ReactLoopAgent`, and every event is demuxed strictly by session id so two sessions streaming at once never interleave. +The **Agent Client Protocol (ACP)** bridge: exposes DeepSeek Harness SDK agents as an ACP server over JSON-RPC stdio, so editors (Zed and other ACP clients) can drive them — streaming render, tool-call display, and resumable sessions. Zed is the current target client: baseline ACP behavior should remain reasonable for other clients, but bridge capabilities and compatibility decisions are evaluated against Zed first. **N concurrent sessions per connection** (see [ACP multi-session](../../../docs/rfc/proposed/feature/2026-06-14-acp-multi-session.md)): each maps to its own `ReactLoopAgent`, and every event is demuxed strictly by session id so two sessions streaming at once never interleave. It is a **client-driver / UI plugin**, the structured analogue of the readline `stdio-chat` plugin — NOT a loop change and NOT a [capability seam](../../../docs/rfc/implemented/architecture/2026-06-13-capability-seams.md). It consumes the existing `agent/*` event taxonomy, the `dsh-agent` create/resume factory, and `dsh-session-persistence`. diff --git a/packages/ui/acp/package.json b/packages/ui/acp/package.json index b4c27f25e2..cdc952558c 100644 --- a/packages/ui/acp/package.json +++ b/packages/ui/acp/package.json @@ -1,6 +1,6 @@ { "name": "@deepseek-ai/dsh-acp", - "description": "Agent Client Protocol (ACP) bridge: drive the DeepSeek Harness coding agent from an ACP editor over JSON-RPC stdio", + "description": "Agent Client Protocol (ACP) bridge: drive DeepSeek Harness SDK agents from an ACP editor over JSON-RPC stdio", "version": "0.0.1", "private": true, "type": "module", diff --git a/packages/web/web-search-deepseek/tests/deepseek.e2e.ts b/packages/web/web-search-deepseek/tests/deepseek.e2e.ts index ae4dc3bf6a..f828fbd12e 100644 --- a/packages/web/web-search-deepseek/tests/deepseek.e2e.ts +++ b/packages/web/web-search-deepseek/tests/deepseek.e2e.ts @@ -28,7 +28,7 @@ maybe('DeepSeekSearchProvider real API', () => { maxTokens: DEEPSEEK_DEFAULT_MAX_TOKENS, maxUses: DEEPSEEK_DEFAULT_MAX_USES, }) - const result = await provider.search({ query: 'What is the DeepSeek coding agent?', maxResults: 5 }) + const result = await provider.search({ query: 'What is the DeepSeek Harness SDK?', maxResults: 5 }) expect(result.providerId).toBe('deepseek') expect(result.sources.length).toBeGreaterThan(0) for (const source of result.sources) expect(source.url).toMatch(/^https?:\/\//) diff --git a/packages/web/web-search-exa/tests/exa.e2e.ts b/packages/web/web-search-exa/tests/exa.e2e.ts index 84c0214228..c9e9233bc0 100644 --- a/packages/web/web-search-exa/tests/exa.e2e.ts +++ b/packages/web/web-search-exa/tests/exa.e2e.ts @@ -16,7 +16,7 @@ maybe('ExaSearchProvider real API', () => { searchType: EXA_DEFAULT_SEARCH_TYPE, highlightsPerResult: EXA_DEFAULT_HIGHLIGHTS_PER_RESULT, }) - const result = await provider.search({ query: 'DeepSeek coding agent', maxResults: 5 }) + const result = await provider.search({ query: 'DeepSeek Harness SDK', maxResults: 5 }) expect(result.providerId).toBe('exa') expect(result.sources.length).toBeGreaterThan(0) for (const source of result.sources) expect(source.url).toMatch(/^https?:\/\//) diff --git a/packages/web/web-search-perplexity/tests/perplexity.e2e.ts b/packages/web/web-search-perplexity/tests/perplexity.e2e.ts index 02aaa914e6..2fc89db7d4 100644 --- a/packages/web/web-search-perplexity/tests/perplexity.e2e.ts +++ b/packages/web/web-search-perplexity/tests/perplexity.e2e.ts @@ -16,7 +16,7 @@ maybe('PerplexitySearchProvider real API', () => { model: process.env.PERPLEXITY_MODEL ?? PERPLEXITY_DEFAULT_MODEL, maxTokens: PERPLEXITY_DEFAULT_MAX_TOKENS, }) - const result = await provider.search({ query: 'What is the DeepSeek coding agent?', maxResults: 5 }) + const result = await provider.search({ query: 'What is the DeepSeek Harness SDK?', maxResults: 5 }) expect(result.providerId).toBe('perplexity') expect(result.content ?? '').not.toBe('') for (const source of result.sources) expect(source.url).toMatch(/^https?:\/\//) From fdd65269f9545c993267f5e427c014fda38dac5d Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 5 Jul 2026 18:51:52 +0800 Subject: [PATCH 12/14] docs: synthesize architecture rewrite --- docs/architecture.md | 152 ++++++++++-------- .../process/2026-06-20-rfc-classification.md | 2 +- packages/bash/bash-local/README.md | 2 +- packages/bash/bash-local/src/index.ts | 5 +- packages/bash/tool-bash/src/index.ts | 2 +- scripts/doc-budgets.manifest.json | 2 +- scripts/verify-doc-refs.ts | 6 +- 7 files changed, 98 insertions(+), 73 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index b0d42d8432..07d952c5d6 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,24 +1,22 @@ # DeepSeek Harness Architecture -DeepSeek Harness SDK is the plugin runtime for building agent harnesses. The governing idea is small and strict: the harness core owns the vocabulary and the turn driver, and every product capability is a Cordis plugin attached through a typed service or event seam. Model adapters, tools, persistence, filesystem access, hooks, UI bridges, compaction, and subagents all enter the system the same way. +The **DeepSeek Harness SDK** is an agent-runtime SDK built microkernel-style on the vendored Cordis framework. The governing principle is simple: **everything is a plugin**. The shipped loop plugin drives the default agent lifecycle, but it is still replaceable; most behavior attaches through typed service and event seams that a replacement loop would honor. -Read this page as the system map before changing `packages/`. It describes behavior: what services exist, how a turn moves through the loop, where plugins extend it, and which invariants keep replay and hot reload sane. Literal type shapes live in [core-data-structures/](core-data-structures/core.md), exact event and service signatures live in the generated [events](cordis-catalog/events.md) and [services](cordis-catalog/services.md) catalogs, visual relationship maps live in the [documentation graph index](graph-atlas.md), and package-level contracts live in the package READMEs from the [package map](../packages/README.md). +Read this page as the system map before changing `packages/`. It covers behavior: services, loop lifecycle, extension seams, and invariants. Type shapes live in [core-data-structures/](core-data-structures/core.md); exact signatures in the generated [events](cordis-catalog/events.md) and [services](cordis-catalog/services.md) catalogs; diagrams in the [documentation graph index](graph-atlas.md); package contracts in the [package map](../packages/README.md); rationale in the [RFCs](rfc/README.md). -## The Mental Model +## Mental Model -A running harness is one Cordis context. Packages contribute three kinds of things to it: +A running harness is one Cordis context. Packages contribute three things to it: - **Services** on `ctx.`: stable call surfaces such as `ctx.llm`, `ctx.tools`, or `ctx.sessions`. -- **Events**: interception and notification seams such as `agent/request`, `tools/pre-execute`, or `session/event`. -- **Registrations**: prompt sections, tool schemas, adapters, providers, and listeners, all installed through `ctx.effect()`, `ctx.on()`, or `ctx.waterfall()` so disposal unwinds them. +- **Events**: typed interception and notification seams such as `agent/request`, `tools/pre-execute`, or `session/event`. +- **Registrations**: prompt sections, tool schemas, adapters, providers, and listeners, all installed through disposable effects so teardown and hot reload unwind them. -The default agent loop is intentionally ordinary: drain queued work, assemble a request, stream a model answer, run tools, decide whether to continue, flush durable state. The important part is where the loop pauses. Every pause is a seam a plugin can program against without reaching into loop internals. - -That rule is the design pressure behind the repo layout. Interface packages own vocabulary and `ctx` keys; implementation packages register concrete backends; consumer packages expose model-facing tools or app-facing bridges. A product feature should usually be a plugin on an existing seam, not a patch to `dsh-agent-loop`. +The default loop is intentionally ordinary: drain queued work, assemble a request, stream a model answer, run tools, decide whether to continue, flush durable state. The important part is where it pauses: each pause is a seam a plugin can program against. ## Service Map -The product spine in [`packages/core/`](../packages/core/README.md) is the minimum language of an agent run: +The default agent spine is assembled from these packages under [`packages/core/`](../packages/core/README.md): | ctx key | Package | Role | |---|---|---| @@ -26,9 +24,11 @@ The product spine in [`packages/core/`](../packages/core/README.md) is the minim | `ctx.systemPrompt` | `dsh-system-prompt` | ordered prompt sections plus tool schemas | | `ctx.tools` | `dsh-tools` | tool registry and execution pipeline | | `ctx.agents` | `dsh-agent` | live agent registry, public `Agent` handle, `agent/*` vocabulary | -| `ctx.agentLoop` | `dsh-agent-loop` | the concrete `ReactLoopAgent` driver | +| `ctx.agentLoop` | `dsh-agent-loop` | the shipped `ReactLoopAgent` driver | -The swappable seams sit around that spine: +Tool schemas ride in prompt assembly, so "what the model is told it can do" stays coherent ([assembly RFC](rfc/implemented/architecture/2026-06-11-tool-schemas-in-prompt-assembly.md)). Tool execution runs through `tools/pre-execute` → dispatch → `tools/post-execute`, the gate pair for sandbox, permission, hook, and plan-mode plugins ([pipeline graph](tool-execution-pipeline.md)). + +The swappable capability seams sit around that spine: | ctx key | Package family | Role | |---|---|---| @@ -40,82 +40,106 @@ The swappable seams sit around that spine: | `ctx.subagents` | [`subagent/`](../packages/subagent/README.md) | named delegation providers | | `ctx.sessionPersistence` | [`session-persistence/`](../packages/session-persistence/README.md) | durable storage for session logs | -`dsh-agent-core` is the sanctioned composition exception to the dependency rule: it depends on the concrete loop because its job is to assemble the default providerless spine. Extension plugins depend on interfaces and event vocabulary, never on `dsh-agent-loop`; swapping the loop means shipping a different bundle, not rewiring every extension. +Extension plugins depend on interfaces and event vocabulary, never on `dsh-agent-loop`; swapping the loop means shipping a different bundle. The sanctioned exception is `dsh-agent-core`, whose job is to compose the default spine. -## Capability Seams +## Cordis In Five Ideas -The default seam shape is **interface / implementation / consumer**. The interface package owns the `ctx` key, abstract service, event vocabulary, and shared types. An implementation package registers one concrete backend. A consumer package, often a `tool-*` package, depends only on the interface and registers model-facing behavior through `ctx.tools` or prompt assembly. The bash family is the reference shape: `dsh-bash`, `dsh-bash-local`, `dsh-tool-bash`. - -Several seams intentionally bend that template. The LLM seam keeps interface and consumer vocabulary together because adapters are the only implementations. The filesystem family adds `dsh-fs-policy` as an event-gate plugin: `dsh-tool-fs` dispatches `fs/write-intent`, `fs/edit-intent`, and `fs/observed`, while the policy listens without becoming a method service the tool must inject. The web seam is one service with search and fetch provider registries, so provider swaps do not rename model tools. The subagent seam is a named provider registry because multiple delegation backends can coexist in one context. +Cordis is vendored source the harness owns ([manifest + sync](../vendor/README.md)). A plugin author needs five ideas: plugins are modules with optional `inject` and `apply(ctx)`, or `Service` subclasses; services own `ctx` keys; `inject` waits for required services; events are typed by declaration merging and dispatch as emit, waterfall, parallel, or serial; registrations are disposable effects. ## Cordis Waterfall Semantics -`ctx.waterfall` is around-middleware, not a reducer. A listener receives `(...args, next)` and chooses one of three behaviors: +`ctx.waterfall` is around-middleware, not a reducer. A listener receives `(...args, next)`: call `next()` to delegate, optionally wrapping the result; return without `next()` to short-circuit; use `prepend: true` only when it must run first. Values propagate through `next()`'s return value. Cooperative listeners mutate a shared object and then delegate; returning a replacement is a takeover because earlier mutations on the old object disappear downstream. For single-slot decision events such as `fs/write-intent`, returning without `next()` is the point: the first decider owns the decision. -- call `next()` to delegate to later listeners and the core behavior, optionally wrapping the result; -- return without calling `next()` to short-circuit with its own result; -- register with `prepend: true` when it must run before existing listeners. +## Event Taxonomy -Values propagate through `next()`'s return value. Cooperative listeners mutate a shared object and then delegate; replacing an object is a takeover, because earlier mutations on the old object will not be seen downstream. For single-slot decision events such as `fs/write-intent`, returning without `next()` is the point: the first decider owns the decision. +Each service declares its own events; `agent/*` lives in `dsh-agent`, so extensions use the live agent vocabulary without depending on the concrete loop. Capability events belong to the seam that owns their vocabulary: `tools/*`, `llm/*`, `system-prompt/*`, `fs/*`, `subagent/*`, and `session/flush`. The generated [events catalog](cordis-catalog/events.md) is exhaustive; [event-producer-consumer.md](event-producer-consumer.md) shows topology. -## Sessions And Messages +Domain rule: `session/*` is durable, replayable fact; `agent/*` is live runtime surface ([event-domain RFC](rfc/implemented/architecture/2026-06-30-event-domain-semantics.md)). Reloadable UI state belongs on the session log; hooks, status observers, request mutation, prompt gating, step-result validation, and continuation policy belong on the live agent surface. -A `Session` is an append-only log of typed `SessionEvent`s. The log is the source of truth for replay, UI rendering, persistence, and derived model history. `deriveMessages()` projects surface events into the `Message[]` sent to the model; raw `assistant/chunk` entries stay in the log for replay and transcript fidelity but do not become prompt history. Persistence backends subscribe to `session/event`, buffer snapshots of appended events, and drain them at the awaited `session/flush` checkpoint. +## Loop Lifecycle (Session / Turn / Step) -Messages are arrays of typed content blocks from `dsh-llm`: `text`, `reasoning`, `tool-call`, and `tool-result`. The block union, message sources, finish reasons, turn triggers, turn-end reasons, and session event variants use the merge-extensible-map pattern documented in [core-data-structures](core-data-structures/core.md). A plugin can extend a map, but every shipping path that observes the new variant must be taught what it means. +A **session** is one agent's append-only event log. A **turn** drains one queued batch and runs until the model stops asking for tools and no plugin requests continuation. A **step** is one model request plus the tool executions caused by that response. In the contract below ([sequence companion](agent-lifecycle.md)), every `'quoted'` line appends a durable session event and every `waterfall`/`serial` line is an extension seam. -## Loop Lifecycle - -The loop uses three nested units: - -- **Session**: the full append-only event log for one agent. -- **Turn**: one drained batch of queued work, running until the model stops asking for tools and no plugin requests continuation. -- **Step**: one model request plus the tool executions caused by that response. - -One turn follows this shape: +### Turn Flow ```text -agent/session-start once per live agent -turn/start durable boundary - agent/prompt-submit allow, rewrite, attach context, or block each queued prompt - system-prompt/assemble sections + tool schemas - agent/pre-step surface mutation before history derivation, e.g. compaction - step/start durable boundary - agent/request mutate the GenerateOptions before the model call - llm/stream stream raw chunks from the selected adapter - assistant/chunk* replay/UI facts - agent/step-result inspect or rewrite the assembled assistant message - assistant/message the message used for tool dispatch and future history - tool/call -> ctx.tools.execute -> tool/result - tools/pre-execute allow, deny, or ask before dispatch - tools/post-execute accept, block, replace output, or attach context - context/message* buffered post-tool context, after all tool results - steering/message* mid-turn steering for the next step - step/end durable boundary - agent/turn-continuation continue or stop -turn/end durable boundary -session/flush awaited durability checkpoint +create agent -> emit agent/session-start(source) once per live agent +forever: + wait for queued messages (idle) + emit agent/status(running) + TURN: + 'turn/start' + each queued msg: waterfall agent/prompt-submit allow, rewrite, attach context, or block + allow -> session('user/message'...); inject additionalContext + every prompt blocked -> 'turn/end'(rejected) zero-step turn, model never called + STEP loop: + drain steering + assembly = ctx.systemPrompt.assemble() waterfall system-prompt/assemble + await ctx.serial('agent/pre-step') surface mutation before history derivation + session('step/start') + req = {model, system, tools, messages: session.deriveMessages(), signal} + req = waterfall agent/request hooks, model switch, tool filtering + stream ctx.llm.stream(req) waterfall llm/stream + session('assistant/chunk') + if assembler.finish is error/aborted: throw + msg = waterfall agent/step-result before the log append + session('assistant/message' {content, usage?}) + each tool-call (sequential, abort-checked between calls): + session('tool/call'); ctx.tools.execute() + tools/pre-execute -> dispatch -> tools/post-execute + tools may append their own session events, e.g. todo/write + session('tool/result') + append buffered post-execute context -> session('context/message')* + drain steering -> session('steering/message') + session('step/end') + cont = waterfall agent/turn-continuation continue iff tool calls or steering by default + continue reasons become next-step steering + if action == stop: break + session('turn/end') + await ctx.parallel('session/flush', session) + leftover steering re-enqueued as queued messages + emit agent/status(idle) unless more queued ``` -Tool calls are sequential, and the loop checks cancellation between calls. Post-tool context is appended after all tool results so the tool-call/result adjacency remains stable. Steering injected while a turn is running is drained between steps; leftover steering after a turn is re-queued so it is never stranded. +Post-tool context lands after all tool results so tool-call/result adjacency stays stable. Steering drains between steps; leftover steering after a turn is re-queued. -## Event Domains +### Failure Boundaries -`session/*` events are durable, replayable facts. Anything a UI can reconstruct after reload, including transcript surface, todo state, hook provenance, compaction records, and crash recovery markers, belongs on the session log or a merge-extensible session event. +The turn is the containment boundary. A throwing listener, adapter error finish, or failed step ends the current turn with an error reason and reports live diagnostics through `agent/error`; it does not kill the driver loop. `cancel()` clears queued and steering work, aborts the active model/tool boundary when possible, and records the appropriate turn end. Disposal stops the loop, awaits quiescence, unregisters the agent, and lets service disposers drain. -`agent/*` events are the live runtime surface. They carry an `Agent` object and power hooks, status observers, request mutation, prompt gating, step-result validation, and continuation policy. The declarations live in `dsh-agent`, not in `dsh-agent-loop`, so plugins can depend on the public agent vocabulary without depending on the concrete loop. +Every session event is turn-enclosed. Reloading a crashed session preserves the interrupted tail and closes it with a synthetic `interrupted` turn end. A failure after `turn/end`, such as a rejecting `session/flush`, reports through `agent/error` only because no safe in-turn position remains. A turn ends with one `TurnEndReason` (`completed`, `aborted`, `error`, `disposed`, `max-tokens`, `rejected`, or `interrupted`); per-variant semantics are in [session.md § TurnEndReasonMap](core-data-structures/session.md#why-a-turn-ended-turnendreasonmap). -Capability events belong to the seam that owns their vocabulary: `tools/*` for tool execution, `llm/*` for model streaming, `system-prompt/*` for assembly, `fs/*` for filesystem policy, `subagent/*` for delegation runs, and `session/flush` for durability. The generated [events catalog](cordis-catalog/events.md) is the exhaustive reference and is freshness-gated. +## Agents And Subagents -## Failure Boundaries +`Agent` is the handle every plugin programs against: `send()` queues work, `steer()` injects mid-turn content, `inject()` appends context and opens a one-shot `injection` turn when idle, `cancel()` is the single public stop primitive, and `whenIdle()` observes quiescence. The factory returns `AgentHandle { agent, dispose() }`; lifecycle owners tear down with `await dispose()`. Full semantics: [core.md](core-data-structures/core.md), [lifecycle RFC](rfc/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-seams.md). -The turn is the loop's containment boundary. A throwing listener, adapter error finish, or failed step ends the current turn with an error reason and reports live diagnostics through `agent/error`; it does not kill the driver loop. `cancel()` clears queued and steering work, aborts the active model/tool boundary when possible, and records the appropriate turn end. Disposal stops the loop, awaits quiescence, unregisters the agent, and lets service disposers drain their work. +**Subagents** are a seam, not an `Agent` method: `ctx.subagents` is a named-provider registry (`spawn` starts fresh, `fork` seeds from the parent's completed-turn prefix, ACP drives an out-of-process child); children are ordinary agents ([subagent.md](core-data-structures/subagent.md), [subagent RFC](rfc/implemented/feature/2026-06-21-subagent-capability-seam.md)). -Every session event is turn-enclosed. A backend that reloads a crashed session preserves the interrupted tail and closes it with a synthetic `interrupted` turn end rather than truncating real work. A failure after `turn/end`, such as a rejecting `session/flush`, is reported through `agent/error` only because there is no safe in-turn position left for a durable session event. +## The Session Log Is The Truth + +A `Session` is the single source of truth. `deriveMessages()` projects surface events into the `Message[]` sent to the model; raw `assistant/chunk` events stay in the log for replay/UI fidelity and are skipped. Every other consumer is a derived view too: replay/fork seeds from events, trace/telemetry listens to `session/event`, and resume goes through `ctx.agents.resume({ resumeSessionId })` ([event-sourcing RFC](rfc/implemented/architecture/2026-06-11-event-sourced-sessions.md)). + +Durability is a plugin concern: `session/event` is synchronous, persistence backends buffer write-behind, and the loop awaits `session/flush` at every turn end. The `SessionPersistence` seam stores `SessionEvent` directly, with metadata in `SessionHeader`; JSONL and SQLite share one contract suite ([persistence RFC](rfc/implemented/architecture/2026-06-14-session-persistence.md), [write-coordinator RFC](rfc/implemented/architecture/2026-06-18-shared-persistence-write-coordinator.md)). + +## Content Blocks And Streaming (dsh-llm) + +Messages are arrays of typed content blocks (`text`, `reasoning`, `tool-call`, `tool-result`). The union derives from the merge-extensible `ContentBlockMap`; the same pattern types `MessageSource`, `FinishReason`, `TurnTrigger`, and `TurnEndReason`. The core set is limited to blocks every shipping path honors; new block types land in one coordinated change across adapters, UI bridges, and compaction pricing ([drop-image RFC](rfc/implemented/simplification/2026-07-04-drop-image-content-block.md)). + +Streaming is a raw chunk protocol (`block-start` through `finish`) with `BlockAssembler` as the shared chunk-to-block assembler. The loop logs raw chunks for replay while assembling them for dispatch. `LlmAdapter` is the provider seam: subclass, implement `stream()`, register with `ctx.llm.registerAdapter(models, adapter)`; `dsh-llm-deepseek` and `dsh-llm-pi-ai` are deliberate design twins ([twin RFC](rfc/implemented/architecture/2026-06-13-twin-llm-adapters.md)). StreamChunk conventions live in [llm-streaming.md](core-data-structures/llm-streaming.md). + +## Capability Seams + +A swappable capability splits into **interface / implementation / consumer**: the interface owns the `ctx` key and vocabulary; an implementation registers a backend; a consumer exposes model-facing behavior through `ctx.tools` or prompt assembly. The bash trio is the reference shape, and the mechanism is plain Cordis services plus `inject` ([capability-seams RFC](rfc/implemented/architecture/2026-06-13-capability-seams.md), [seam graph](capability-seams.md)). + +Some seams bend the template deliberately. LLM keeps interface and consumer vocabulary together because adapters are the implementations. Filesystem adds policy as an event gate: `dsh-tool-fs` dispatches `fs/write-intent`, `fs/edit-intent`, and `fs/observed`, while `dsh-fs-policy` listens without becoming a method service ([event-gate RFC](rfc/implemented/architecture/2026-06-26-file-context-as-event-gate.md)). Web is one service with search and fetch provider registries, so provider swaps do not rename model tools ([web-seam RFC](rfc/implemented/architecture/2026-06-24-web-capability-seam.md)). Subagents use a named provider registry because multiple delegation backends can coexist. + +## Composition + +`dsh-agent-core` is the composition bundle: one plugin loading the providerless spine as code ([README](../packages/core/agent-core/README.md)). App packages compose it with a front door and own the boot `bin`: `dsh-stdio-agent` for the terminal REPL, and `dsh-acp-agent` for ACP over JSON-RPC stdio with no stdout logger ([ui/](../packages/ui/README.md), [app-extraction RFC](rfc/implemented/architecture/2026-06-20-extract-example-app-packages.md)). A deployment is a thin `cordis.yml` leaf: swappable backends, one app entry, and optional product tools ([examples/](../examples/AGENTS.md), [runnable wirings](cookbook/extension-cookbook.md#runnable-wirings), [graph atlas](graph-atlas.md)). ## Extending The Harness -Start from the extension point, not the loop: +New behavior should attach to a documented seam; changing `dsh-agent-loop` itself requires updating this map. | Goal | Mechanism | |---|---| @@ -126,4 +150,4 @@ Start from the extension point, not the loop: | Add UI or editor integration | drive `ctx.agents` and render from `session/event` | | Add durable session state | add a `SessionEventMap` member and render/replay from the log | -The [extension cookbook](cookbook/extension-cookbook.md) maps common features to seams, and the step-by-step guides cover [packages](cookbook/adding-a-package.md), [tools](cookbook/adding-a-tool.md), [LLM adapters](cookbook/adding-an-llm-adapter.md), and [vendored packages](cookbook/adding-a-vendored-package.md). When a change seems to require editing `dsh-agent-loop`, first name the missing seam; if the loop really changes, update this map in the same PR. +The [extension cookbook](cookbook/extension-cookbook.md) carries plugin skeletons and the feature-to-seam map; step-by-step guides cover [packages](cookbook/adding-a-package.md), [tools](cookbook/adding-a-tool.md), [LLM adapters](cookbook/adding-an-llm-adapter.md), and [vendored packages](cookbook/adding-a-vendored-package.md). diff --git a/docs/rfc/implemented/process/2026-06-20-rfc-classification.md b/docs/rfc/implemented/process/2026-06-20-rfc-classification.md index 21d4129fac..8fcc101b7d 100644 --- a/docs/rfc/implemented/process/2026-06-20-rfc-classification.md +++ b/docs/rfc/implemented/process/2026-06-20-rfc-classification.md @@ -30,7 +30,7 @@ The `architecture` / `process` line: **architecture** is about the source we shi Both are `doc-sync` members, in the `verify-md-wrap` style (tsx ESM, verify-don't-generate, exit non-zero on the first violation): - **`scripts/verify-rfc-classification.ts`** — the closed set and index freshness. It asserts every file under a lifecycle folder lives in a class folder from the canonical set (a loose `.md` at a lifecycle root, or an unknown class folder, fails), and that the README's marker-delimited index regions byte-match a fresh render from the tree (see [generate the RFC index tables](2026-07-04-generate-rfc-index-tables.md)). The canonical class set lives as a `const` in `scripts/rfc-index.ts` — the machine source of truth shared with the generator — and [the index](../../README.md) documents it in prose; the README's class *descriptions* stay hand-written, its tables are generated. -- **`scripts/verify-doc-refs.ts`** — source comments that cite docs. RFC paths are referenced not only from Markdown but from TypeScript doc comments (root-relative prose like `docs/rfc/implemented/testing/2026-06-19-acp-snapshot-tests.md`). `verify-md-links` never saw those, so the reorg could have silently orphaned them. This gate scans repo-authored `.ts` under `packages/**` and `examples/**` (excluding built `lib/` and `vendor/`) for `docs/….md` tokens, resolves each root-relative, and asserts it exists. It requires the `.md` extension so extensionless prose (`docs/postmortem/0001`, `docs/architecture.md § plugin checklist`) is left alone. +- **`scripts/verify-doc-refs.ts`** — source comments that cite docs. RFC paths are referenced not only from Markdown but from TypeScript doc comments (root-relative prose like `docs/rfc/implemented/testing/2026-06-19-acp-snapshot-tests.md`). `verify-md-links` never saw those, so the reorg could have silently orphaned them. This gate scans repo-authored `.ts` under `packages/**` and `examples/**` (excluding built `lib/` and `vendor/`) for `docs/….md` tokens, resolves each root-relative, and asserts it exists. It requires the `.md` extension so extensionless prose (`docs/postmortem/0001`, `docs/architecture.md § Extending The Harness`) is left alone. ### Rejected alternatives diff --git a/packages/bash/bash-local/README.md b/packages/bash/bash-local/README.md index c13a3ab923..dec29ce93b 100644 --- a/packages/bash/bash-local/README.md +++ b/packages/bash/bash-local/README.md @@ -27,4 +27,4 @@ Design surveyed against the bash tools of Claude Code, OpenCode, Codex, and pi; ## Sandboxing -`TODO(permissions/sandbox)`: execution policy does NOT belong in this package. Use the `tools/pre-execute` deny/ask gate or implement a sandboxing `BashExecutor` — see docs/architecture.md § plugin checklist. Reference points: Claude Code wraps commands in sandbox-exec/bubblewrap; Codex applies seatbelt/landlock plus an execpolicy prefix-rule engine. +`TODO(permissions/sandbox)`: execution policy does NOT belong in this package. Use the `tools/pre-execute` deny/ask gate or implement a sandboxing `BashExecutor` — see docs/architecture.md § Extending The Harness. Reference points: Claude Code wraps commands in sandbox-exec/bubblewrap; Codex applies seatbelt/landlock plus an execpolicy prefix-rule engine. diff --git a/packages/bash/bash-local/src/index.ts b/packages/bash/bash-local/src/index.ts index 6df7b3da7b..cdac3985b8 100644 --- a/packages/bash/bash-local/src/index.ts +++ b/packages/bash/bash-local/src/index.ts @@ -5,8 +5,9 @@ * survey notes), tracks background tasks, and kills everything on dispose. * * TODO(permissions/sandbox): execution policy does NOT belong here — use - * the `tools/pre-execute` deny/ask gate (see docs/architecture.md § plugin - * checklist) or implement a sandboxing `BashExecutor`. Reference points: + * the `tools/pre-execute` deny/ask gate (see docs/architecture.md + * § Extending The Harness) or implement a sandboxing `BashExecutor`. + * Reference points: * Claude Code wraps commands in sandbox-exec/bubblewrap; Codex applies * seatbelt/landlock plus an execpolicy prefix-rule engine. * diff --git a/packages/bash/tool-bash/src/index.ts b/packages/bash/tool-bash/src/index.ts index 88d14cc0f0..aa7bce2727 100644 --- a/packages/bash/tool-bash/src/index.ts +++ b/packages/bash/tool-bash/src/index.ts @@ -33,7 +33,7 @@ * TODO(permissions): commands run with the executor's full authority. The * permission/sandbox seam is the `tools/pre-execute` waterfall (deny/ask) plus * sandboxing `BashExecutor` implementations — see docs/architecture.md - * § plugin checklist. + * § Extending The Harness. * * @module @deepseek-ai/dsh-tool-bash */ diff --git a/scripts/doc-budgets.manifest.json b/scripts/doc-budgets.manifest.json index 4a13f5a0fd..72e1f3d594 100644 --- a/scripts/doc-budgets.manifest.json +++ b/scripts/doc-budgets.manifest.json @@ -1,7 +1,7 @@ { "AGENTS.md": 1575, "docs/AGENTS.md": 1315, - "docs/architecture.md": 1650, + "docs/architecture.md": 1840, "docs/defensive-patterns.md": 550, "docs/testing.md": 800, "examples/AGENTS.md": 610, diff --git a/scripts/verify-doc-refs.ts b/scripts/verify-doc-refs.ts index 322368b724..9a5714ef1d 100644 --- a/scripts/verify-doc-refs.ts +++ b/scripts/verify-doc-refs.ts @@ -2,7 +2,7 @@ * Doc-sync gate: verify that doc references written in TypeScript COMMENTS * resolve to a file that exists. Source comments cite docs by root-relative * prose path — `see docs/rfc/implemented/testing/2026-06-19-acp-snapshot-tests.md`, - * `docs/architecture.md § plugin checklist`. `verify-md-links` parses Markdown + * `docs/architecture.md § Extending The Harness`. `verify-md-links` parses Markdown * link AST and never sees these, so a doc rename or move could silently orphan * a `.ts` comment that points at it. The RFC classification reorg * ([the classification RFC](../docs/rfc/implemented/process/2026-06-20-rfc-classification.md)) @@ -12,7 +12,7 @@ * Detection is a token scan, NOT an AST walk: doc refs live in free prose inside * comments, not in a structured form. We match `docs/.md` tokens and * REQUIRE the `.md` extension, so extensionless prose (`docs/postmortem/0001`, - * `docs/architecture.md § plugin checklist` — the section suffix is outside the + * `docs/architecture.md § Extending The Harness` — the section suffix is outside the * token) is left alone rather than misread as a path. Each token is resolved * ROOT-RELATIVE (the way the comments are written) and must exist on disk. This * is checker, not fixer: it reports and never rewrites. @@ -43,7 +43,7 @@ const isExcluded = (p: string): boolean => * Match a `docs/…​.md` reference token. The `.md` extension is required so a * bare `docs/postmortem/0001` (no extension) does not register as a path. The * character class stops at whitespace, backticks, parens, and the section sign, - * so trailing prose (`… .md § plugin checklist`) is not swallowed into the path. + * so trailing prose (`… .md § Extending The Harness`) is not swallowed into the path. */ const DOC_REF = /\bdocs\/[A-Za-z0-9._/-]+\.md/g From d899333cde07c96ece6ab39c98913d7c06f60a0e Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 5 Jul 2026 19:07:34 +0800 Subject: [PATCH 13/14] docs: split cordis primer from architecture map --- AGENTS.md | 4 +- docs/architecture.md | 141 ++++++++++++++---------------- docs/cordis-catalog/events.md | 2 +- docs/cordis-primer.md | 38 ++++++++ scripts/doc-budgets.manifest.json | 3 +- scripts/gen-cordis-catalog.ts | 2 +- scripts/verify-doc-refs.ts | 6 +- 7 files changed, 113 insertions(+), 83 deletions(-) create mode 100644 docs/cordis-primer.md diff --git a/AGENTS.md b/AGENTS.md index 9521dee491..bfb4060e5e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # AGENTS.md -This is the monorepo of the DeepSeek Harness group; it hosts **DeepSeek Harness SDK**, a plugin-based SDK for building agent harnesses. The codebase is built on the vendored Cordis framework, microkernel-style: **everything is a plugin**. Read [docs/architecture.md](docs/architecture.md) before changing anything under `packages/` — the service map, event taxonomy, loop lifecycle, and extension seams. The documentation standard is [docs/AGENTS.md](docs/AGENTS.md). +This is the monorepo of the DeepSeek Harness group; it hosts **DeepSeek Harness SDK**, a plugin-based SDK for building agent harnesses. The codebase is built on the vendored Cordis framework, microkernel-style: **everything is a plugin**. Read [docs/architecture.md](docs/architecture.md) before changing anything under `packages/` — the service map, event surface, loop lifecycle, and extension seams. The documentation standard is [docs/AGENTS.md](docs/AGENTS.md). ## Pre-release stance: foundation over blast radius @@ -85,7 +85,7 @@ Real-API tests and demos read `DEEPSEEK_API_KEY` (and optional `DEEPSEEK_BASE_UR - **Registrations are effects**: every contribution goes through `ctx.effect()` / `ctx.on()`; a registry's `register()` returns the disposer. - **Typed events via declaration merging**; extensible unions use the merge-extensible-map pattern (`ContentBlockMap`, `SessionEventMap`, …). Every new event's JSDoc carries an `@mode` tag and a `@param` per payload parameter (`this`/trailing `next` exempt); every public service-class method documents each parameter and non-void return (`@param`/`@returns`) — the catalog generator hard-errors otherwise ([completeness RFC](docs/rfc/implemented/process/2026-07-04-cordis-jsdoc-completeness-gate.md)); mode semantics are in the [generated events catalog](docs/cordis-catalog/events.md) header and [the catalog RFC](docs/rfc/implemented/process/2026-06-20-generated-cordis-catalog.md). - **Discriminated unions: `switch` on the tag**, not if-chains. Closed unions end with `default: assertNever(...)`; merge-extensible unions must NOT — handle known cases and fall through `default` with a comment. -- **Waterfall listeners MUST call `next()`** to delegate; returning without it is the veto ([semantics](docs/architecture.md#cordis-waterfall-semantics)). +- **Waterfall listeners MUST call `next()`** to delegate; returning without it is the veto ([semantics](docs/cordis-primer.md#cordis-waterfall-semantics)). - **Plugins, not loop changes**: new behavior goes on the documented extension seams; changing `agent-loop` requires updating docs/architecture.md. - **Capability seams are three packages** — interface / implementation / consumer ([capability seams](docs/rfc/implemented/architecture/2026-06-13-capability-seams.md)); don't split preemptively. - **Explicit > implicit at package seams**: no optional field silently filled by a hidden `?? default` inside `run()`; defaulting is an explicit `resolve(request): Spec` step in the owning implementation (the `dsh-bash` request/spec split is the template). diff --git a/docs/architecture.md b/docs/architecture.md index 07d952c5d6..304773914d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,22 +1,16 @@ # DeepSeek Harness Architecture -The **DeepSeek Harness SDK** is an agent-runtime SDK built microkernel-style on the vendored Cordis framework. The governing principle is simple: **everything is a plugin**. The shipped loop plugin drives the default agent lifecycle, but it is still replaceable; most behavior attaches through typed service and event seams that a replacement loop would honor. +The **DeepSeek Harness SDK** is an agent-runtime SDK built microkernel-style on the vendored Cordis framework. The governing principle is simple: **everything is a plugin**. The shipped agent loop is one plugin in the default bundle, not a privileged kernel; most behavior attaches through typed service and event seams that another loop plugin can honor. -Read this page as the system map before changing `packages/`. It covers behavior: services, loop lifecycle, extension seams, and invariants. Type shapes live in [core-data-structures/](core-data-structures/core.md); exact signatures in the generated [events](cordis-catalog/events.md) and [services](cordis-catalog/services.md) catalogs; diagrams in the [documentation graph index](graph-atlas.md); package contracts in the [package map](../packages/README.md); rationale in the [RFCs](rfc/README.md). +Read this page as the system map before changing `packages/`. It explains how the runtime is shaped, how the default loop moves work, where state lives, and where extensions attach. Type shapes live in [core-data-structures/](core-data-structures/core.md); exact event and service signatures live in the generated [events](cordis-catalog/events.md) and [services](cordis-catalog/services.md) catalogs; package contracts live in the [package map](../packages/README.md); rationale lives in the [RFCs](rfc/README.md). If Cordis itself is new to you, start with the [Cordis primer](cordis-primer.md). -## Mental Model +## System Shape -A running harness is one Cordis context. Packages contribute three things to it: +A running harness is one Cordis context. Packages contribute service keys, typed events, and disposable registrations to that context. Services are the stable call surfaces (`ctx.llm`, `ctx.tools`, `ctx.sessions`); events are interception and notification points (`agent/request`, `tools/pre-execute`, `session/event`); registrations install prompt sections, tool schemas, providers, adapters, and listeners. -- **Services** on `ctx.`: stable call surfaces such as `ctx.llm`, `ctx.tools`, or `ctx.sessions`. -- **Events**: typed interception and notification seams such as `agent/request`, `tools/pre-execute`, or `session/event`. -- **Registrations**: prompt sections, tool schemas, adapters, providers, and listeners, all installed through disposable effects so teardown and hot reload unwind them. +The default distribution is a composition, not a hierarchy. `packages/core/` is a repository grouping for the default agent spine; capability seams around it are equally first-class plugins. Swapping the loop means shipping a different composition bundle, while ordinary extensions should depend on the public services and event vocabulary. -The default loop is intentionally ordinary: drain queued work, assemble a request, stream a model answer, run tools, decide whether to continue, flush durable state. The important part is where it pauses: each pause is a seam a plugin can program against. - -## Service Map - -The default agent spine is assembled from these packages under [`packages/core/`](../packages/core/README.md): +### Default Service Spine | ctx key | Package | Role | |---|---|---| @@ -24,122 +18,119 @@ The default agent spine is assembled from these packages under [`packages/core/` | `ctx.systemPrompt` | `dsh-system-prompt` | ordered prompt sections plus tool schemas | | `ctx.tools` | `dsh-tools` | tool registry and execution pipeline | | `ctx.agents` | `dsh-agent` | live agent registry, public `Agent` handle, `agent/*` vocabulary | -| `ctx.agentLoop` | `dsh-agent-loop` | the shipped `ReactLoopAgent` driver | +| `ctx.agentLoop` | `dsh-agent-loop` | shipped `ReactLoopAgent` driver | -Tool schemas ride in prompt assembly, so "what the model is told it can do" stays coherent ([assembly RFC](rfc/implemented/architecture/2026-06-11-tool-schemas-in-prompt-assembly.md)). Tool execution runs through `tools/pre-execute` → dispatch → `tools/post-execute`, the gate pair for sandbox, permission, hook, and plan-mode plugins ([pipeline graph](tool-execution-pipeline.md)). +Tool schemas ride in prompt assembly, so "what the model is told it can do" stays coherent with the tools registry. Tool execution runs through `tools/pre-execute` -> dispatch -> `tools/post-execute`, the gate pair for sandbox, permission, hook, and plan-mode plugins ([pipeline graph](tool-execution-pipeline.md)). -The swappable capability seams sit around that spine: +### Capability Services | ctx key | Package family | Role | |---|---|---| | `ctx.llm` | [`llm/`](../packages/llm/README.md) | adapter registry and streaming model calls | | `ctx.bash` | [`bash/`](../packages/bash/README.md) | foreground/background command execution | -| `ctx.fs` | [`fs/`](../packages/fs/README.md) | filesystem provider primitives; `fs/*` policy events | +| `ctx.fs` | [`fs/`](../packages/fs/README.md) | filesystem provider primitives and policy events | | `ctx.web` | [`web/`](../packages/web/README.md) | search/fetch provider registries | | `ctx.compact` | [`compact/`](../packages/compact/README.md) | session-surface compaction | | `ctx.subagents` | [`subagent/`](../packages/subagent/README.md) | named delegation providers | | `ctx.sessionPersistence` | [`session-persistence/`](../packages/session-persistence/README.md) | durable storage for session logs | -Extension plugins depend on interfaces and event vocabulary, never on `dsh-agent-loop`; swapping the loop means shipping a different bundle. The sanctioned exception is `dsh-agent-core`, whose job is to compose the default spine. +## Event Surface -## Cordis In Five Ideas +Events are the harness extension API. Each service owns the vocabulary for the behavior it controls, and the generated [events catalog](cordis-catalog/events.md) is the exhaustive reference. The [producer/consumer map](event-producer-consumer.md) shows which packages emit or listen to each event. -Cordis is vendored source the harness owns ([manifest + sync](../vendor/README.md)). A plugin author needs five ideas: plugins are modules with optional `inject` and `apply(ctx)`, or `Service` subclasses; services own `ctx` keys; `inject` waits for required services; events are typed by declaration merging and dispatch as emit, waterfall, parallel, or serial; registrations are disposable effects. +### Event Domains -## Cordis Waterfall Semantics +Use the event domain to decide where new behavior belongs: -`ctx.waterfall` is around-middleware, not a reducer. A listener receives `(...args, next)`: call `next()` to delegate, optionally wrapping the result; return without `next()` to short-circuit; use `prepend: true` only when it must run first. Values propagate through `next()`'s return value. Cooperative listeners mutate a shared object and then delegate; returning a replacement is a takeover because earlier mutations on the old object disappear downstream. For single-slot decision events such as `fs/write-intent`, returning without `next()` is the point: the first decider owns the decision. +- **Session events** are durable, replayable facts. Turn and step boundaries, user input, assistant output, tool calls, tool results, steering, compaction records, and tool-owned durable facts append to the session log and flow through `session/event`. +- **Agent events** are live runtime surfaces. They carry the live `Agent` handle for status, diagnostics, prompt admission, request mutation, result validation, and continuation policy. +- **Capability events** belong to the seam that owns the action. `tools/*`, `llm/*`, `system-prompt/*`, `fs/*`, and `subagent/*` let policy and adapters attach without importing the loop. -## Event Taxonomy +### Interception Semantics -Each service declares its own events; `agent/*` lives in `dsh-agent`, so extensions use the live agent vocabulary without depending on the concrete loop. Capability events belong to the seam that owns their vocabulary: `tools/*`, `llm/*`, `system-prompt/*`, `fs/*`, `subagent/*`, and `session/flush`. The generated [events catalog](cordis-catalog/events.md) is exhaustive; [event-producer-consumer.md](event-producer-consumer.md) shows topology. +Waterfall events behave like around-middleware: a listener delegates by calling `next()` and vetoes or takes over by returning without it. The full rule lives in [Cordis waterfall semantics](cordis-primer.md#cordis-waterfall-semantics). -Domain rule: `session/*` is durable, replayable fact; `agent/*` is live runtime surface ([event-domain RFC](rfc/implemented/architecture/2026-06-30-event-domain-semantics.md)). Reloadable UI state belongs on the session log; hooks, status observers, request mutation, prompt gating, step-result validation, and continuation policy belong on the live agent surface. +## Default Loop Lifecycle -## Loop Lifecycle (Session / Turn / Step) +The shipped loop drains queued work, assembles a request, streams a model answer, executes tools, decides whether to continue, and checkpoints durable state. The important architecture is where it pauses: each pause is a documented service call or event seam that another plugin can program against. -A **session** is one agent's append-only event log. A **turn** drains one queued batch and runs until the model stops asking for tools and no plugin requests continuation. A **step** is one model request plus the tool executions caused by that response. In the contract below ([sequence companion](agent-lifecycle.md)), every `'quoted'` line appends a durable session event and every `waterfall`/`serial` line is an extension seam. +A **session** is one agent's append-only event log. A **turn** drains one queued batch and runs until the model stops asking for tools and no plugin requests continuation. A **step** is one model request plus the tool executions caused by that response. In the flow below ([sequence companion](agent-lifecycle.md)), quoted names are durable session events and event names are extension seams. + +### Agent Handles + +`ctx.agents` owns live agents and returns an `AgentHandle { agent, dispose() }`. `Agent` is the surface other plugins drive: `send()` queues work, `steer()` injects mid-turn content, `inject()` appends context and opens a one-shot injection turn when idle, `cancel()` is the public stop primitive, and `whenIdle()` observes quiescence. Lifecycle owners tear down with `await dispose()`. ### Turn Flow ```text -create agent -> emit agent/session-start(source) once per live agent +create agent -> emit agent/session-start(source) forever: - wait for queued messages (idle) + wait for queued messages emit agent/status(running) TURN: 'turn/start' - each queued msg: waterfall agent/prompt-submit allow, rewrite, attach context, or block - allow -> session('user/message'...); inject additionalContext - every prompt blocked -> 'turn/end'(rejected) zero-step turn, model never called + each queued message -> agent/prompt-submit + allowed prompt -> 'user/message' plus injected context + every prompt blocked -> 'turn/end'(rejected) STEP loop: drain steering - assembly = ctx.systemPrompt.assemble() waterfall system-prompt/assemble - await ctx.serial('agent/pre-step') surface mutation before history derivation - session('step/start') - req = {model, system, tools, messages: session.deriveMessages(), signal} - req = waterfall agent/request hooks, model switch, tool filtering - stream ctx.llm.stream(req) waterfall llm/stream - session('assistant/chunk') - if assembler.finish is error/aborted: throw - msg = waterfall agent/step-result before the log append - session('assistant/message' {content, usage?}) - each tool-call (sequential, abort-checked between calls): - session('tool/call'); ctx.tools.execute() - tools/pre-execute -> dispatch -> tools/post-execute - tools may append their own session events, e.g. todo/write - session('tool/result') - append buffered post-execute context -> session('context/message')* - drain steering -> session('steering/message') - session('step/end') - cont = waterfall agent/turn-continuation continue iff tool calls or steering by default - continue reasons become next-step steering - if action == stop: break - session('turn/end') - await ctx.parallel('session/flush', session) - leftover steering re-enqueued as queued messages - emit agent/status(idle) unless more queued + assemble system prompt and tool schemas + agent/pre-step + 'step/start' + derive messages from the session log + agent/request -> llm/stream + 'assistant/chunk' + agent/step-result + 'assistant/message' + each tool call: + 'tool/call' + tools/pre-execute -> dispatch -> tools/post-execute + 'tool/result' + append post-tool context and steering + 'step/end' + agent/turn-continuation + stop unless tools or continuation policy ask for another step + 'turn/end' + checkpoint persistence and notify idle/running status ``` -Post-tool context lands after all tool results so tool-call/result adjacency stays stable. Steering drains between steps; leftover steering after a turn is re-queued. +Post-tool context lands after all tool results so tool-call/result adjacency stays stable. Steering drains between steps; leftover steering after a turn is re-queued as ordinary input. ### Failure Boundaries The turn is the containment boundary. A throwing listener, adapter error finish, or failed step ends the current turn with an error reason and reports live diagnostics through `agent/error`; it does not kill the driver loop. `cancel()` clears queued and steering work, aborts the active model/tool boundary when possible, and records the appropriate turn end. Disposal stops the loop, awaits quiescence, unregisters the agent, and lets service disposers drain. -Every session event is turn-enclosed. Reloading a crashed session preserves the interrupted tail and closes it with a synthetic `interrupted` turn end. A failure after `turn/end`, such as a rejecting `session/flush`, reports through `agent/error` only because no safe in-turn position remains. A turn ends with one `TurnEndReason` (`completed`, `aborted`, `error`, `disposed`, `max-tokens`, `rejected`, or `interrupted`); per-variant semantics are in [session.md § TurnEndReasonMap](core-data-structures/session.md#why-a-turn-ended-turnendreasonmap). +Every session event is turn-enclosed. Reloading a crashed session preserves the interrupted tail and closes it with a synthetic `interrupted` turn end. A failure after the durable turn has closed reports through `agent/error` only because no safe in-turn position remains. A turn ends with one `TurnEndReason` (`completed`, `aborted`, `error`, `disposed`, `max-tokens`, `rejected`, or `interrupted`); per-variant semantics are in [session.md § TurnEndReasonMap](core-data-structures/session.md#why-a-turn-ended-turnendreasonmap). -## Agents And Subagents +## State And Model Surface -`Agent` is the handle every plugin programs against: `send()` queues work, `steer()` injects mid-turn content, `inject()` appends context and opens a one-shot `injection` turn when idle, `cancel()` is the single public stop primitive, and `whenIdle()` observes quiescence. The factory returns `AgentHandle { agent, dispose() }`; lifecycle owners tear down with `await dispose()`. Full semantics: [core.md](core-data-structures/core.md), [lifecycle RFC](rfc/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-seams.md). +### Session Log -**Subagents** are a seam, not an `Agent` method: `ctx.subagents` is a named-provider registry (`spawn` starts fresh, `fork` seeds from the parent's completed-turn prefix, ACP drives an out-of-process child); children are ordinary agents ([subagent.md](core-data-structures/subagent.md), [subagent RFC](rfc/implemented/feature/2026-06-21-subagent-capability-seam.md)). +The session log is the source of truth. `deriveMessages()` projects session events into the `Message[]` sent to the model; raw `assistant/chunk` events stay in the log for replay and UI fidelity. Replay, fork, resume, transcript rendering, telemetry, and persistence all derive from the same event stream. -## The Session Log Is The Truth +Durability is a plugin concern. Persistence backends buffer synchronous `session/event` notifications and the loop awaits a turn-end checkpoint before moving on. The `SessionPersistence` seam stores `SessionEvent` directly, with metadata in `SessionHeader`; JSONL and SQLite share one contract suite. -A `Session` is the single source of truth. `deriveMessages()` projects surface events into the `Message[]` sent to the model; raw `assistant/chunk` events stay in the log for replay/UI fidelity and are skipped. Every other consumer is a derived view too: replay/fork seeds from events, trace/telemetry listens to `session/event`, and resume goes through `ctx.agents.resume({ resumeSessionId })` ([event-sourcing RFC](rfc/implemented/architecture/2026-06-11-event-sourced-sessions.md)). +### Model Content -Durability is a plugin concern: `session/event` is synchronous, persistence backends buffer write-behind, and the loop awaits `session/flush` at every turn end. The `SessionPersistence` seam stores `SessionEvent` directly, with metadata in `SessionHeader`; JSONL and SQLite share one contract suite ([persistence RFC](rfc/implemented/architecture/2026-06-14-session-persistence.md), [write-coordinator RFC](rfc/implemented/architecture/2026-06-18-shared-persistence-write-coordinator.md)). +Messages are arrays of typed content blocks (`text`, `reasoning`, `tool-call`, `tool-result`). The union derives from the merge-extensible `ContentBlockMap`; the same pattern types `MessageSource`, `FinishReason`, `TurnTrigger`, and `TurnEndReason`. New block types are coordinated across adapters, UI bridges, compaction pricing, and persistence, so block vocabulary remains a repo-wide contract. -## Content Blocks And Streaming (dsh-llm) +Streaming is a raw chunk protocol (`block-start` through `finish`) with `BlockAssembler` as the shared chunk-to-block assembler. The loop logs raw chunks while assembling them for dispatch. `LlmAdapter` is the provider seam: subclass, implement `stream()`, and register with `ctx.llm.registerAdapter(models, adapter)`. StreamChunk conventions live in [llm-streaming.md](core-data-structures/llm-streaming.md). -Messages are arrays of typed content blocks (`text`, `reasoning`, `tool-call`, `tool-result`). The union derives from the merge-extensible `ContentBlockMap`; the same pattern types `MessageSource`, `FinishReason`, `TurnTrigger`, and `TurnEndReason`. The core set is limited to blocks every shipping path honors; new block types land in one coordinated change across adapters, UI bridges, and compaction pricing ([drop-image RFC](rfc/implemented/simplification/2026-07-04-drop-image-content-block.md)). +## Extension And Composition -Streaming is a raw chunk protocol (`block-start` through `finish`) with `BlockAssembler` as the shared chunk-to-block assembler. The loop logs raw chunks for replay while assembling them for dispatch. `LlmAdapter` is the provider seam: subclass, implement `stream()`, register with `ctx.llm.registerAdapter(models, adapter)`; `dsh-llm-deepseek` and `dsh-llm-pi-ai` are deliberate design twins ([twin RFC](rfc/implemented/architecture/2026-06-13-twin-llm-adapters.md)). StreamChunk conventions live in [llm-streaming.md](core-data-structures/llm-streaming.md). +### Capability Pattern -## Capability Seams +A swappable capability usually splits into **interface / implementation / consumer**: the interface owns the `ctx` key and vocabulary; an implementation registers a backend; a consumer exposes model-facing behavior through `ctx.tools` or prompt assembly. The bash trio is the reference shape, and the [capability seam graph](capability-seams.md) shows the current package families. -A swappable capability splits into **interface / implementation / consumer**: the interface owns the `ctx` key and vocabulary; an implementation registers a backend; a consumer exposes model-facing behavior through `ctx.tools` or prompt assembly. The bash trio is the reference shape, and the mechanism is plain Cordis services plus `inject` ([capability-seams RFC](rfc/implemented/architecture/2026-06-13-capability-seams.md), [seam graph](capability-seams.md)). +Some seams bend the template deliberately. LLM keeps interface and consumer vocabulary together because adapters are the implementations. Filesystem adds policy as event gates around provider primitives. Web is one service with search and fetch provider registries, so provider swaps do not rename model tools. Subagents use a named provider registry because multiple delegation backends can coexist; `spawn` starts fresh, `fork` seeds from the parent's completed-turn prefix, and ACP can drive an out-of-process child ([subagent.md](core-data-structures/subagent.md)). -Some seams bend the template deliberately. LLM keeps interface and consumer vocabulary together because adapters are the implementations. Filesystem adds policy as an event gate: `dsh-tool-fs` dispatches `fs/write-intent`, `fs/edit-intent`, and `fs/observed`, while `dsh-fs-policy` listens without becoming a method service ([event-gate RFC](rfc/implemented/architecture/2026-06-26-file-context-as-event-gate.md)). Web is one service with search and fetch provider registries, so provider swaps do not rename model tools ([web-seam RFC](rfc/implemented/architecture/2026-06-24-web-capability-seam.md)). Subagents use a named provider registry because multiple delegation backends can coexist. +### Bundles And Apps -## Composition +`dsh-agent-core` is the default composition bundle: one plugin loading the providerless spine as code ([README](../packages/core/agent-core/README.md)). App packages compose it with a front door and own the boot `bin`: `dsh-stdio-agent` for the terminal REPL, and `dsh-acp-agent` for ACP over JSON-RPC stdio with no stdout logger ([ui/](../packages/ui/README.md)). A deployment is a thin `cordis.yml` leaf: swappable backends, one app entry, and optional product tools ([examples/](../examples/AGENTS.md), [runnable wirings](cookbook/extension-cookbook.md#runnable-wirings), [graph atlas](graph-atlas.md)). -`dsh-agent-core` is the composition bundle: one plugin loading the providerless spine as code ([README](../packages/core/agent-core/README.md)). App packages compose it with a front door and own the boot `bin`: `dsh-stdio-agent` for the terminal REPL, and `dsh-acp-agent` for ACP over JSON-RPC stdio with no stdout logger ([ui/](../packages/ui/README.md), [app-extraction RFC](rfc/implemented/architecture/2026-06-20-extract-example-app-packages.md)). A deployment is a thin `cordis.yml` leaf: swappable backends, one app entry, and optional product tools ([examples/](../examples/AGENTS.md), [runnable wirings](cookbook/extension-cookbook.md#runnable-wirings), [graph atlas](graph-atlas.md)). +### Where New Behavior Goes -## Extending The Harness - -New behavior should attach to a documented seam; changing `dsh-agent-loop` itself requires updating this map. +New behavior should attach to a documented seam; changing the shipped loop requires updating this map. | Goal | Mechanism | |---|---| diff --git a/docs/cordis-catalog/events.md b/docs/cordis-catalog/events.md index b2b2e08858..f05b156da4 100644 --- a/docs/cordis-catalog/events.md +++ b/docs/cordis-catalog/events.md @@ -9,7 +9,7 @@ This file is GENERATED from source (`scripts/gen-cordis-catalog.ts`) and verifie The **harness tier** below (the `@deepseek-ai/dsh-*` packages) is the vocabulary this repo owns, grouped by scope. The **inherited tier** at the end is the cordis-core + loader/hmr/timer event surface a plugin also sees — pinned vendor source, summarized tersely. -Dispatch modes: **emit** (fire-and-forget), **waterfall** (each listener gets `next()` and may transform or veto — see [waterfall semantics](../architecture.md#cordis-waterfall-semantics)), **parallel** (awaited fan-out; all listeners run), **serial** (awaited in registration order until one returns a bail value — anything other than `null`, `false`, or `undefined`). +Dispatch modes: **emit** (fire-and-forget), **waterfall** (each listener gets `next()` and may transform or veto — see [waterfall semantics](../cordis-primer.md#cordis-waterfall-semantics)), **parallel** (awaited fan-out; all listeners run), **serial** (awaited in registration order until one returns a bail value — anything other than `null`, `false`, or `undefined`). ## `agent/*` diff --git a/docs/cordis-primer.md b/docs/cordis-primer.md new file mode 100644 index 0000000000..15534b0ae6 --- /dev/null +++ b/docs/cordis-primer.md @@ -0,0 +1,38 @@ +# Cordis Primer + +Cordis is the vendored plugin framework underneath the DeepSeek Harness SDK. This primer teaches the Cordis ideas a harness plugin author needs before reading the generated [events](cordis-catalog/events.md) and [services](cordis-catalog/services.md) catalogs. The vendored source and sync procedure live in [vendor/README.md](../vendor/README.md). + +## Cordis In Five Ideas + +- **A plugin is a unit of behavior.** It can be a function with optional `inject` and `apply(ctx)` fields, or a `Service` subclass whose lifecycle Cordis mounts into the current context. +- **A context is the service container.** A service claims a stable `ctx.` such as `ctx.tools`, `ctx.llm`, or `ctx.sessions`; other plugins program against that key instead of importing a concrete implementation. +- **`inject` is the dependency gate.** A plugin that names required services waits until those services exist, so load order is expressed through service requirements rather than manual boot sequencing. +- **Events are typed extension seams.** Services declare event names through TypeScript declaration merging, then dispatch them as `emit`, `waterfall`, `parallel`, or `serial` depending on whether listeners observe, wrap, fan out, or run in order. +- **Registrations are disposable effects.** Prompt sections, tool schemas, adapters, providers, and listeners are installed through `ctx.effect()` or `ctx.on()` so reload and teardown unwind them predictably. + +## Dispatch Modes + +Use the mode to understand what a listener can do: + +| Mode | Shape | +|---|---| +| `emit` | synchronous notification; listeners observe but do not shape the result | +| `waterfall` | around-middleware; each listener receives `next()` and may wrap, rewrite, or veto | +| `parallel` | awaited fan-out; all listeners run and the dispatcher waits for them | +| `serial` | awaited in registration order; a non-void bail value stops the chain | + +The mode is part of the event's public contract. New harness events document it with an `@mode` tag so the generated catalog can check declarations against dispatch sites. + +## Cordis Waterfall Semantics + +`ctx.waterfall` is around-middleware, not a reducer. A listener receives `(...args, next)`. Call `next()` to delegate, optionally wrapping the result; return without `next()` to short-circuit. Values propagate through `next()`'s return value. + +Cooperative listeners usually mutate a shared request or decision object and then delegate. Returning a replacement is a takeover: downstream listeners see the replacement, and earlier mutations on the original object do not carry forward. Use `prepend: true` only when the listener must run before ordinary registrations. + +For single-decision events, short-circuiting is the design. A policy listener can return without `next()` when it owns the decision, while a listener that only annotates or observes must delegate. + +## Practical Rules + +Own vocabulary where the behavior lives: a tool pipeline event belongs to `ctx.tools`, model streaming belongs to `ctx.llm`, and live agent coordination belongs to `ctx.agents`. Prefer events for interception and policy; prefer service methods for direct capability calls. + +Every registration should have a disposer, either by returning one from `ctx.effect()` or using a Cordis helper that does it for you. If teardown order matters, keep the related work in one effect so disposal unwinds in the intended sequence. diff --git a/scripts/doc-budgets.manifest.json b/scripts/doc-budgets.manifest.json index 72e1f3d594..b43f5fa8fc 100644 --- a/scripts/doc-budgets.manifest.json +++ b/scripts/doc-budgets.manifest.json @@ -1,7 +1,8 @@ { "AGENTS.md": 1575, "docs/AGENTS.md": 1315, - "docs/architecture.md": 1840, + "docs/architecture.md": 1630, + "docs/cordis-primer.md": 550, "docs/defensive-patterns.md": 550, "docs/testing.md": 800, "examples/AGENTS.md": 610, diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts index 74d4f6e00b..acb6579272 100644 --- a/scripts/gen-cordis-catalog.ts +++ b/scripts/gen-cordis-catalog.ts @@ -556,7 +556,7 @@ function renderEvents(events: EventEntry[]): string { '', 'The **harness tier** below (the `@deepseek-ai/dsh-*` packages) is the vocabulary this repo owns, grouped by scope. The **inherited tier** at the end is the cordis-core + loader/hmr/timer event surface a plugin also sees — pinned vendor source, summarized tersely.', '', - 'Dispatch modes: **emit** (fire-and-forget), **waterfall** (each listener gets `next()` and may transform or veto — see [waterfall semantics](../architecture.md#cordis-waterfall-semantics)), **parallel** (awaited fan-out; all listeners run), **serial** (awaited in registration order until one returns a bail value — anything other than `null`, `false`, or `undefined`).', + 'Dispatch modes: **emit** (fire-and-forget), **waterfall** (each listener gets `next()` and may transform or veto — see [waterfall semantics](../cordis-primer.md#cordis-waterfall-semantics)), **parallel** (awaited fan-out; all listeners run), **serial** (awaited in registration order until one returns a bail value — anything other than `null`, `false`, or `undefined`).', '', ] const scopes = [...new Set(events.map(e => e.scope))].sort() diff --git a/scripts/verify-doc-refs.ts b/scripts/verify-doc-refs.ts index 9a5714ef1d..a53399a272 100644 --- a/scripts/verify-doc-refs.ts +++ b/scripts/verify-doc-refs.ts @@ -2,7 +2,7 @@ * Doc-sync gate: verify that doc references written in TypeScript COMMENTS * resolve to a file that exists. Source comments cite docs by root-relative * prose path — `see docs/rfc/implemented/testing/2026-06-19-acp-snapshot-tests.md`, - * `docs/architecture.md § Extending The Harness`. `verify-md-links` parses Markdown + * `docs/architecture.md § Where New Behavior Goes`. `verify-md-links` parses Markdown * link AST and never sees these, so a doc rename or move could silently orphan * a `.ts` comment that points at it. The RFC classification reorg * ([the classification RFC](../docs/rfc/implemented/process/2026-06-20-rfc-classification.md)) @@ -12,7 +12,7 @@ * Detection is a token scan, NOT an AST walk: doc refs live in free prose inside * comments, not in a structured form. We match `docs/.md` tokens and * REQUIRE the `.md` extension, so extensionless prose (`docs/postmortem/0001`, - * `docs/architecture.md § Extending The Harness` — the section suffix is outside the + * `docs/architecture.md § Where New Behavior Goes` — the section suffix is outside the * token) is left alone rather than misread as a path. Each token is resolved * ROOT-RELATIVE (the way the comments are written) and must exist on disk. This * is checker, not fixer: it reports and never rewrites. @@ -43,7 +43,7 @@ const isExcluded = (p: string): boolean => * Match a `docs/…​.md` reference token. The `.md` extension is required so a * bare `docs/postmortem/0001` (no extension) does not register as a path. The * character class stops at whitespace, backticks, parens, and the section sign, - * so trailing prose (`… .md § Extending The Harness`) is not swallowed into the path. + * so trailing prose (`… .md § Where New Behavior Goes`) is not swallowed into the path. */ const DOC_REF = /\bdocs\/[A-Za-z0-9._/-]+\.md/g From e154396612adb59aec8e7c523cd8f133d7fa09d3 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 5 Jul 2026 19:52:07 +0800 Subject: [PATCH 14/14] docs: refine architecture extension map --- docs/architecture.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index 304773914d..4f2a3f603b 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,6 +1,6 @@ # DeepSeek Harness Architecture -The **DeepSeek Harness SDK** is an agent-runtime SDK built microkernel-style on the vendored Cordis framework. The governing principle is simple: **everything is a plugin**. The shipped agent loop is one plugin in the default bundle, not a privileged kernel; most behavior attaches through typed service and event seams that another loop plugin can honor. +The **DeepSeek Harness SDK** is an SDK for building agent harnesses using the Cordis framework. The governing principle is simple: **everything is a plugin**. For example, the shipped agent loop is just one plugin in the default bundle, not a privileged kernel. Read this page as the system map before changing `packages/`. It explains how the runtime is shaped, how the default loop moves work, where state lives, and where extensions attach. Type shapes live in [core-data-structures/](core-data-structures/core.md); exact event and service signatures live in the generated [events](cordis-catalog/events.md) and [services](cordis-catalog/services.md) catalogs; package contracts live in the [package map](../packages/README.md); rationale lives in the [RFCs](rfc/README.md). If Cordis itself is new to you, start with the [Cordis primer](cordis-primer.md). @@ -8,7 +8,7 @@ Read this page as the system map before changing `packages/`. It explains how th A running harness is one Cordis context. Packages contribute service keys, typed events, and disposable registrations to that context. Services are the stable call surfaces (`ctx.llm`, `ctx.tools`, `ctx.sessions`); events are interception and notification points (`agent/request`, `tools/pre-execute`, `session/event`); registrations install prompt sections, tool schemas, providers, adapters, and listeners. -The default distribution is a composition, not a hierarchy. `packages/core/` is a repository grouping for the default agent spine; capability seams around it are equally first-class plugins. Swapping the loop means shipping a different composition bundle, while ordinary extensions should depend on the public services and event vocabulary. +The default distribution is a composition, not a hierarchy. `packages/core/` is a repository grouping for the default agent spine; capability seams around it are equally first-class plugins from a Cordis perspective. ### Default Service Spine @@ -16,12 +16,10 @@ The default distribution is a composition, not a hierarchy. `packages/core/` is |---|---|---| | `ctx.sessions` | `dsh-session` | in-memory event-sourced sessions | | `ctx.systemPrompt` | `dsh-system-prompt` | ordered prompt sections plus tool schemas | -| `ctx.tools` | `dsh-tools` | tool registry and execution pipeline | +| `ctx.tools` | `dsh-tools` | tool registry and [execution pipeline](tool-execution-pipeline.md) | | `ctx.agents` | `dsh-agent` | live agent registry, public `Agent` handle, `agent/*` vocabulary | | `ctx.agentLoop` | `dsh-agent-loop` | shipped `ReactLoopAgent` driver | -Tool schemas ride in prompt assembly, so "what the model is told it can do" stays coherent with the tools registry. Tool execution runs through `tools/pre-execute` -> dispatch -> `tools/post-execute`, the gate pair for sandbox, permission, hook, and plan-mode plugins ([pipeline graph](tool-execution-pipeline.md)). - ### Capability Services | ctx key | Package family | Role | @@ -56,10 +54,6 @@ The shipped loop drains queued work, assembles a request, streams a model answer A **session** is one agent's append-only event log. A **turn** drains one queued batch and runs until the model stops asking for tools and no plugin requests continuation. A **step** is one model request plus the tool executions caused by that response. In the flow below ([sequence companion](agent-lifecycle.md)), quoted names are durable session events and event names are extension seams. -### Agent Handles - -`ctx.agents` owns live agents and returns an `AgentHandle { agent, dispose() }`. `Agent` is the surface other plugins drive: `send()` queues work, `steer()` injects mid-turn content, `inject()` appends context and opens a one-shot injection turn when idle, `cancel()` is the public stop primitive, and `whenIdle()` observes quiescence. Lifecycle owners tear down with `await dispose()`. - ### Turn Flow ```text @@ -102,6 +96,10 @@ The turn is the containment boundary. A throwing listener, adapter error finish, Every session event is turn-enclosed. Reloading a crashed session preserves the interrupted tail and closes it with a synthetic `interrupted` turn end. A failure after the durable turn has closed reports through `agent/error` only because no safe in-turn position remains. A turn ends with one `TurnEndReason` (`completed`, `aborted`, `error`, `disposed`, `max-tokens`, `rejected`, or `interrupted`); per-variant semantics are in [session.md § TurnEndReasonMap](core-data-structures/session.md#why-a-turn-ended-turnendreasonmap). +### Agent Handles + +`ctx.agents` owns live agents and returns an `AgentHandle { agent, dispose() }`. `Agent` is the surface other plugins drive: `send()` queues work, `steer()` injects mid-turn content, `inject()` appends context and opens a one-shot injection turn when idle, `cancel()` is the public stop primitive, and `whenIdle()` observes quiescence. Lifecycle owners tear down with `await dispose()`. + ## State And Model Surface ### Session Log @@ -136,7 +134,8 @@ New behavior should attach to a documented seam; changing the shipped loop requi |---|---| | Add a model provider | register an adapter on `ctx.llm` | | Add a model-facing capability | register a tool on `ctx.tools`; schemas flow into prompt assembly | -| Add an executor or storage backend | implement the owning seam and register the service | +| Add command execution | implement and register a `ctx.bash` backend | +| Add filesystem access or policy | implement a `ctx.fs` provider or listen on `fs/*` policy events | | Intercept prompts, requests, tool use, or continuation | listen on the relevant `agent/*` or `tools/*` waterfall | | Add UI or editor integration | drive `ctx.agents` and render from `session/event` | | Add durable session state | add a `SessionEventMap` member and render/replay from the log |