Merge remote-tracking branch 'origin/master' into codex/project-instruction-files

# Conflicts:
#	AGENTS.md
#	docs/config-catalog.md
#	docs/cordis-catalog/events.md
#	docs/cordis-catalog/services.md
#	docs/core-data-structures/core.md
#	docs/event-producer-consumer.md
#	docs/persistence-catalog.md
#	docs/rfc/implemented/architecture/2026-07-05-reconstructable-requests.md
#	docs/rfc/implemented/feature/2026-06-15-code-mode.md
#	docs/rfc/implemented/feature/2026-06-30-hook-bridges.md
#	docs/rfc/implemented/feature/2026-06-30-interception-seams.md
#	docs/rfc/implemented/feature/2026-07-08-repeat-tool-guard.md
#	docs/rfc/proposed/simplification/2026-07-04-prune-dead-core-spine-surface.md
#	examples/AGENTS.md
#	examples/acp-agent/cordis.yml
#	examples/acp-agent/tests/acp.snapshot.ts
#	examples/echo-agent/cordis.yml
#	examples/sandbox-acp-agent/cordis.yml
#	packages/cordis/tool-cordis/src/api-catalog.ts
#	packages/core/agent-core/README.md
#	packages/core/agent-core/src/index.ts
#	packages/core/agent-loop/README.md
#	packages/core/agent-loop/src/loop.ts
#	packages/core/agent-loop/tests/interception.spec.ts
#	packages/core/agent/src/types.ts
#	packages/core/tools/README.md
#	packages/core/tools/src/code-mode.ts
#	packages/core/tools/src/index.ts
#	packages/fs/fs-local/src/index.ts
#	packages/fs/fs/README.md
#	packages/fs/fs/src/index.ts
#	packages/guard/repeat-tool-guard/README.md
#	packages/guard/repeat-tool-guard/src/index.ts
#	packages/hooks/hooks-claude/src/index.ts
#	packages/hooks/hooks-codex/src/index.ts
#	packages/ui/acp-agent/src/index.ts
This commit is contained in:
Yichen Jiang
2026-07-14 19:50:25 +08:00
720 changed files with 21199 additions and 14129 deletions

View File

@@ -19,6 +19,7 @@ Everything else is documented on a **sub-page**, not here. The rule that draws t
| [scope.md](scope.md) | scoped registration identity, dispatch carriers, and the owned `Scope` context |
| [session.md](session.md) | the full `SessionEventMap` variant catalog, `TurnTrigger`/`TurnEndReason`, `deriveMessages()`, the turn-enclosure invariant |
| [persistence.md](persistence.md) | the durability seam: `SessionPersistence`, JSONL + SQLite backends, `session/flush`, crash recovery, `SessionHeader` |
| [session-query.md](session-query.md) | logical session/event records and bounded exact-event reads |
| [system-prompt.md](system-prompt.md) | per-assembly context, tool-provider results, prompt sections, and cooperative assembly |
| [tools.md](tools.md) | `ToolDefinition` full fields, the schema DSL, `ToolExecution`/`ToolResult`, tool-presentation UI types, and the guarded execution pipeline |
| [user-interaction.md](user-interaction.md) | the UI-backed human question/answer seam: `AskUserQuestionRequest`, answer/options vocabulary, provider API, error taxonomy |
@@ -156,17 +157,8 @@ interface GenerateOptions {
stop?: string[]
signal?: AbortSignal
/**
* The id of the session this request belongs to — stamped by the agent loop
* from `agent.session.id`. Adapters ignore it; it lets an `llm/stream` listener
* route a call by WHICH session issued it (the replay adapter keys its per-call
* cursor by session, so a parent and its in-process subagent — each with its
* own session on one context — replay from their own recorded scripts).
*
* Typed as `Branded<'SessionId'>` rather than importing `SessionId` from
* `dsh-session`: that package imports `Message` from here, so importing its
* `SessionId` back would cycle. `SessionId` IS `Branded<'SessionId'>`, so a
* real session id assigns with no cast. (A future ids package could own the
* brand and dissolve this note.)
* Session identity stamped by the loop for listener routing. Adapters ignore
* it; replay uses it to keep concurrent parent and child cursors independent.
*/
sessionId?: Branded<'SessionId'>
}
@@ -201,7 +193,9 @@ The model-facing `ToolSchema` is the wire shape; the registered `ToolDefinition`
### The request envelope: `LlmCallConfig` and the logged header
Requests are built by the loop, not shaped per call: the non-history half of a request — the `EpochHeader`: this call configuration plus the rendered system prompt, the tool schemas in the authoritative returned assembly order (initially canonicalized by dsh-system-prompt's `toolOrder` config, or lexicographically when unset), and the session prefix — is logged session state (`request/header` snapshot and delta events, [session.md](session.md#the-request-header-events-requestheader-and-requestheader-delta)), so every conversation request is a pure function of the session log ([reconstructability RFC](../rfc/implemented/architecture/2026-07-05-reconstructable-requests.md)). The `agent/request` waterfall receives a frozen `LlmCallConfig` seed and a listener returns a replacement to switch model or sampling; the `agent/session-prefix` waterfall — fired once per loop instance — composes the request-only messages fronting the derived history (recorded as the header's `messagePrefix`) — the loop logs whatever the request actually uses. Loop-built requests arrive at `llm/stream` deep-frozen; mutation throws.
The loop builds each request from logged state. `EpochHeader` records call config, rendered prompt, authoritative returned tool order (configured by `toolOrder`, or lexicographic when unset), and session prefix through `request/header` snapshots and deltas. Together with derived history, this makes the request reconstructable from the session log. See [session.md](session.md#the-request-header-events-requestheader-and-requestheader-delta) and the [reconstructability RFC](../rfc/implemented/architecture/2026-07-05-reconstructable-requests.md).
`agent/request` receives a frozen call-config seed and may return a replacement. `agent/session-prefix` composes request-only prefix messages once per loop instance, and the header records the exact result used. Requests reaching `llm/stream` are deep-frozen, so mutation throws.
On the wire, a loop-built request reads in this order: the `system` slot (the rendered prompt assembly) → `messagePrefix` (the frozen session prefix) → the derived history — the boundary snapshot, whose tail is the newest `user/message` on a turn's first step and the previous step's tool results on later steps. The prefix never enters the derived history; its durable record is the header events, and the dev invariant recomputes exactly this equation against every loop-built request.
@@ -364,7 +358,9 @@ interface Agent {
}
```
`AgentStatus` is `'idle' | 'running' | 'disposed'`. `AgentId` is a branded string. `AgentOptions` (`model?`) is merge-extensible — plugins add creation options by declaration merging. Persona is not an agent option: the `dsh-system-prompt` config supplies the global default, and an agent-scoped `deployment:persona` section may shadow it. The `agent/*` event taxonomy (lifecycle emits incl. `agent/session-start`, serial `agent/pre-step`/`agent/turn-stop` checkpoints, and the `agent/prompt-submit`/`agent/request`/`agent/session-prefix`/`agent/step-result`/`agent/turn-continuation` waterfalls) is in [architecture.md § Event taxonomy](../architecture.md#event-taxonomy); turn/step boundaries are durable `session/event` records, not `agent/*` emits.
`AgentStatus` is `'idle' | 'running' | 'disposed'`, and `AgentId` is branded. `AgentOptions` is merge-extensible and currently includes `model?`. Persona belongs to `dsh-system-prompt`: an agent-scoped `deployment:persona` may shadow the global default.
The [event taxonomy](../architecture.md#event) owns the `agent/*` lifecycle, checkpoint, and waterfall contracts. Turn and step boundaries are durable session events rather than agent emits.
## Interception decisions
@@ -409,7 +405,7 @@ type ContinuationStop = Extract<ContinuationDecision, { action: 'stop' }>
type SessionStartSource = 'startup' | 'resume' | 'clear' | 'compact'
```
`agent/session-prefix` composes the session prefix — a plain `Message[]`, no dedicated payload type. Fired ONCE per loop instance, lazily on its first request: the composed list is deep-frozen, recorded as the header's `messagePrefix` ([the request envelope](#the-request-envelope-llmcallconfig-and-the-logged-header)), and placed in front of the ENTIRE derived history on every request the instance sends — the home for session-stable openers like a skills catalog or an AGENTS.md digest, never returned by `deriveMessages()`. Reuse is structural, so the prefix cannot drift mid-session (resume = a new instance = a recompose); content that changes mid-session goes through the append-only history channels instead (`agent.inject()` and tool/prompt-submit `additionalContexts`). Not a Decision union: the seam contributes content instead of vetoing, so the shape is the contribution itself.
`agent/session-prefix` composes a `Message[]` once per loop instance. The deep-frozen result is recorded in the request header and prepended to every derived history, making it the home for session-stable openers. A resumed instance recomposes; mid-session changes use append-only context channels. The waterfall returns content directly because it contributes rather than decides.
## `ToolDefinition`