docs: address model experience review

This commit is contained in:
Tianyi Cui
2026-07-12 02:55:26 +08:00
parent a08485fc80
commit b5cd511f35
64 changed files with 443 additions and 410 deletions

View File

@@ -2,13 +2,6 @@
The model-facing **`workflow` tool**: run a JavaScript orchestration script that fans out subagents, and return the script's final value. Pure schema + lifecycle shaping over [`ctx.workflows`](../workflow/README.md) — script parsing, execution, caps, and cancellation live behind the seam, so a hardened engine swaps in without touching what the model sees.
## Model Experience
| Context surface | What the model sees | Token effect |
|---|---|---|
| System prompt and tool schema | The parent model receives a short use-only-for-large-orchestration section plus the `workflow` schema. The schema description carries the complete JavaScript hook and metadata contract; the model submits script, metadata, and optional args. | Substantial but fixed per-request guidance and schema cost while visible. |
| Tool-call history and result | The full model-written script, metadata, and args remain in the assistant tool call. The result contains the workflow name, child count, and final JSON value or a shaped error; intermediate child messages are omitted. | Call tokens can be large and remain until compaction. Result rendering is capped by `maxResultChars`; child-model tokens are separate from the parent's retained context. |
## What the model sees
Three parameters: `script` (required plain-JavaScript body with no `export const meta` statement), `meta` (required JSON identity with `name`/`description` and optional `whenToUse`/`phases`), and `args` (optional JSON object exposed to the script as the `args` global; wrap a bare list as a field so the wire schema stays honest). The tool description carries the complete authoring contract: hooks, semantics, and the supported schema subset. The plugin also contributes a `tool:<toolName>` system-prompt section carrying the usage policy — use the tool only on an explicit user ask for a workflow / large orchestration; prefer plain subagent calls for one or two delegations — per the convention that tool guidance ships with the tool plugin, never in the deployment persona.
@@ -28,6 +21,13 @@ Decided up front (per the [render-intent RFC](../../../docs/rfc/implemented/arch
| `toolName` | `workflow` | The model-facing tool name to register. |
| `maxResultChars` | `50000` | Rendered-result ceiling; longer JSON is truncated with a notice. |
## Model Experience
| Context surface | What the model sees | Token effect |
|---|---|---|
| System prompt and tool schema | The parent model receives a short use-only-for-large-orchestration section plus the `workflow` schema. The schema description carries the complete JavaScript hook and metadata contract; the model submits script, metadata, and optional args. | Substantial but fixed per-request guidance and schema cost while visible. |
| Tool-call history and result | The full model-written script, metadata, and args remain in the assistant tool call. The result contains the workflow name, child count, and final JSON value or a shaped error; intermediate child messages are omitted. | Call tokens can be large and remain until compaction. Result rendering is capped by `maxResultChars`; child-model tokens are separate from the parent's retained context. |
## Known Limitations and Deferred Work
- **The parent turn blocks until the whole workflow settles** — there is no background start/poll surface, and cancellation discards partial output as an error.

View File

@@ -2,13 +2,6 @@
The [`WorkflowService`](../workflow/README.md) implementation, on **`node:worker_threads`**: each run gets its OWN worker thread (one run = one worker, no pooling — a run is heavyweight, so the ~tens-of-ms thread spin-up is noise), the script executes in a vm context INSIDE that worker with the workflow hooks injected, and every `agent()` call bridges back over the message port to [`ctx.subagents`](../../subagent/README.md) on the host. Child agents are I/O-bound LLM loops and stay on the host event loop; the thread isolates the SCRIPT, the only part that can spin synchronously.
## Model Experience
| Context surface | What the model sees | Token effect |
|---|---|---|
| Child-agent requests | Every script `agent()` call sends its prompt and optional model or structured-output schema to a subagent provider. Each child sees that provider's own context; phase and log narration stays on observer events. | Potentially many independent child contexts are paid, bounded by `maxConcurrentAgents`, `maxTotalAgents`, and `maxItemsPerCall`; they never join the parent history directly. |
| Parent tool result, indirectly | The parent sees only the script's materialized final JSON value, child count, or error through `dsh-tool-workflow`. Intermediate child outputs are available to the script but not the parent model. | Zero direct parent tokens from this engine. Final result size is capped by the tool consumer and retained until compaction. |
## Trust premise: what the thread buys (and what it does not)
Workflow scripts are **model-written** — the same trust level as the model's existing bash access — so this engine defends against **buggy** scripts, never hostile ones. A worker thread is NOT a security boundary: the vm context inside it is escapable by construction (`node:vm` shares object machinery with its surrounding realm, so a script can reach the `Function` constructor via `globalThis.constructor.constructor` and from it `process` and every Node builtin), and an escapee holds the same process privileges as the host — Node's permission model is process-wide. The absent globals are API surface that keeps honest scripts portable, not walls. What the thread concretely buys:
@@ -59,6 +52,13 @@ A returned promise or thenable resolves per JavaScript semantics BEFORE material
| `syncTimeoutMs` | `5000` | vm timeout for the script's initial synchronous slice (in the worker). |
| `disposeGraceMs` | `5000` | How long a cancelled run may stay unsettled before force-settle + terminate; also bounds `dispose()`. |
## Model Experience
| Context surface | What the model sees | Token effect |
|---|---|---|
| Child-agent requests | Every script `agent()` call sends its prompt and optional model or structured-output schema to a subagent provider. Each child sees that provider's own context; phase and log narration stays on observer events. | Potentially many independent child contexts are paid, bounded by `maxConcurrentAgents`, `maxTotalAgents`, and `maxItemsPerCall`; they never join the parent history directly. |
| Parent tool result, indirectly | The parent sees only the script's materialized final JSON value, child count, or error through `dsh-tool-workflow`. Intermediate child outputs are available to the script but not the parent model. | Zero direct parent tokens from this engine. Final result size is capped by the tool consumer and retained until compaction. |
## Known Limitations and Deferred Work
- **The worker/vm is not a security boundary** — model-written code can escape `node:vm` and reach the worker's process authority; a hostile-code deployment needs a separate-process or container engine.

View File

@@ -2,12 +2,6 @@
The **workflow seam** (`ctx.workflows`): an abstract service defining WHAT a workflow engine does — execute a model-written orchestration script that fans out subagents — without saying HOW. The bash-shaped third of the [workflow family](../README.md): implementations subclass `WorkflowService` and register as the `workflows` service (one per context); [`dsh-workflow-workerthread`](../workflow-workerthread/README.md) (one worker thread per run) is the implementation, and [`dsh-tool-workflow`](../tool-workflow/README.md) is the model-facing consumer.
## Model Experience
| Context surface | What the model sees | Token effect |
|---|---|---|
| None directly | The service seam and `workflow/*` observer events register no prompt, schema, or message. `dsh-tool-workflow` renders the parent-facing contract and final value; an engine decides which child prompts run. | Zero direct tokens. Parent result and child contexts affect tokens only through the consumer and implementation. |
## Service: `WorkflowService` (abstract)
`start(request: WorkflowStartRequest): WorkflowRun` — parse and execute a script. Throws synchronously (`SCRIPT_PARSE`/`META_INVALID`) for a script that cannot begin; once a run is returned, its `result` NEVER rejects — every failure resolves with `stopReason: 'error'` (or `'cancelled'`) — and once the run is cancelled, `result` settles within the implementation's bounded grace even if the script itself never settles (a consumer awaiting `result` must never be wedged past a cancellation). `dispose()` must reach quiescence within a bounded grace (cancel → wait for the script to settle and its children to finish disposing → abandon), never hanging its caller. Runs are HOLDER-owned: the engine does not track its live runs, so disposing the engine's fiber mid-run leaves each run to its holder's teardown.
@@ -30,6 +24,12 @@ All observe-only emits carrying DATA SNAPSHOTS (`WorkflowRunInfo` = id + meta)
- `workflow/phase`(info, title) / `workflow/log`(info, message) — script narration.
- `workflow/agent-start`(info, agent) / `workflow/agent-end`(info, agent + outcome) — ready-child lifecycle correlated by `seq`; the [generated event contract](../../../docs/cordis-catalog/events.md#workflowagent-start--emit) defines publication and pairing.
## Model Experience
| Context surface | What the model sees | Token effect |
|---|---|---|
| None directly | The service seam and `workflow/*` observer events register no prompt, schema, or message. `dsh-tool-workflow` renders the parent-facing contract and final value; an engine decides which child prompts run. | Zero direct tokens. Parent result and child contexts affect tokens only through the consumer and implementation. |
## Known Limitations and Deferred Work
- **Foreground collection only** — the caller owns one live run and awaits it; background start/poll, spill handles, and detached collection are deferred.