Merge remote-tracking branch 'origin/master' into codex/rfc-subagent-background-tasks

# Conflicts:
#	docs/architecture.md
#	docs/config-catalog.md
#	docs/cordis-catalog/services.md
#	docs/core-data-structures/bash.md
#	docs/event-producer-consumer.md
#	docs/rfc/implemented/feature/2026-07-06-sandbox.md
#	docs/rfc/proposed/architecture/2026-06-20-generic-long-running-tool-runtime.md
#	examples/AGENTS.md
#	examples/acp-agent/tests/snapshots/advanced-toolchain/session.1.jsonl
#	examples/acp-agent/tests/snapshots/advanced-toolchain/session.2.jsonl
#	examples/acp-agent/tests/snapshots/advanced-toolchain/session.jsonl
#	examples/acp-agent/tests/snapshots/advanced-toolchain/system-prompt.golden.md
#	examples/acp-agent/tests/snapshots/both-mode-turn/session.jsonl
#	examples/acp-agent/tests/snapshots/both-mode-turn/system-prompt.golden.md
#	examples/acp-agent/tests/snapshots/code-mode-turn/system-prompt.golden.md
#	examples/acp-agent/tests/snapshots/permission-switching/system-prompt.golden.md
#	examples/acp-agent/tests/snapshots/skill-load/session.jsonl
#	examples/acp-agent/tests/snapshots/skill-load/system-prompt.golden.md
#	examples/acp-agent/tests/snapshots/text-turn/session.jsonl
#	examples/acp-agent/tests/snapshots/text-turn/system-prompt.golden.md
#	examples/coding-agent/cordis.yml
#	examples/sandbox-acp-agent/tests/snapshots/escalation-approved/session.jsonl
#	examples/sandbox-acp-agent/tests/snapshots/escalation-rejected/session.jsonl
#	examples/sandbox-acp-agent/tests/snapshots/mode-switching/session.jsonl
#	packages/bash/bash-local/README.md
#	packages/bash/bash-local/src/index.ts
#	packages/bash/bash-local/tests/executor.spec.ts
#	packages/bash/bash-sandbox/README.md
#	packages/bash/bash-sandbox/src/index.ts
#	packages/bash/bash/README.md
#	packages/bash/bash/src/index.ts
#	packages/bash/bash/src/types.ts
#	packages/bash/tool-bash/README.md
#	packages/bash/tool-bash/src/index.ts
#	packages/bash/tool-bash/tests/tools.spec.ts
#	packages/cordis/tool-cordis/src/api-catalog.ts
#	packages/core/agent-core/README.md
#	packages/core/agent-core/src/index.ts
#	packages/subagent/subagent/README.md
#	packages/subagent/tool-subagent/README.md
#	packages/subagent/tool-subagent/src/index.ts
#	packages/ui/acp-agent/README.md
#	packages/ui/acp/README.md
#	packages/ui/stdio-agent/README.md
#	packages/util/brand/src/index.ts
#	scripts/doc-budgets.manifest.json
This commit is contained in:
Yichen Jiang
2026-07-14 18:05:46 +08:00
669 changed files with 10081 additions and 13745 deletions
+26
View File
@@ -35,3 +35,29 @@ A non-`completed` stop reason becomes an `isError` tool result; partial child ou
`run_in_background: true` refuses an already-aborted `exec.signal`, synchronously registers `{ kind: 'subagent', label: description, owner: parent, cancel, done }` with `ctx.tasks`, and returns `started background subagent task <id>`. The starter immediately calls async `ctx.subagents.start()` with an independent `AbortController`; `task_kill` and owner-scope teardown abort that signal whether startup is still pending or the child is ready. The task is final-output-only, and `done` settles only after startup rollback or `run.dispose()` reaches quiescence. Mapping: `runOutcome` turns `completed` into final output, `aborted` into `killed`, and other terminal reasons into `failed`; `settleRun` contains infrastructure and disposal failures. A missing task runtime fails loud. See the [background subagent tasks RFC](../../../docs/rfc/implemented/feature/2026-07-08-background-subagent-tasks.md).
`toolFilter` changes the child's visible global tool layer; it is not a parent-derived authority ceiling. See the [agent-scope security non-goal](../../../docs/rfc/implemented/architecture/2026-07-08-agent-scope-contexts.md#security-and-authority-are-non-goals).
## Model Experience
### Tool schemas
**What the model sees**: While the configured provider exists, the model sees the generated default [`subagent` schema](../../../docs/tool-catalog.md#deepseek-aidsh-tool-subagent) under this instance's configured `toolName`. Fresh-context and inherited-context providers change the tool and `prompt` descriptions; `enableRunInBackground: true` adds `run_in_background` and its generic-task guidance.
**Token effect**: Fixed schema cost per parent request while mounted; each additional provider instance contributes one independently named schema.
### Foreground result
**What the model sees**: The parent tool call retains the task description and prompt. Success contains only the child's data-dependent final text; non-completed stop reasons and infrastructure failures become `Error: <message>`. Intermediate child steps never enter the parent.
**Token effect**: The prompt and final result remain in parent history until compaction; child working context is paid only in the child.
### Background task result
**What the model sees**: Start returns exactly `started background subagent task <id>`. The generic task control surface owns later status, final output, cancellation responses, and completion notices; the child still contributes only its final text on successful collection.
**Token effect**: The start acknowledgement is small and retained. Final output and generic task status enter parent history only when collected or injected by `dsh-tool-tasks`.
## Known Limitations and Deferred Work
- **Background runs expose final output only** — intermediate child steps remain in the child session and cannot be streamed through `task_output`.
- **Duplicate `toolName` across waiting loads is detected late** (`TODO(subagent-dup-toolname)`) — two instances waiting on providers collide only when a provider arrives; config-time detection needs a cross-fiber registry of intended names.
- **Child policy is fixed per tool registration** — model, persona, tool filter, and depth cap come from plugin config; another policy requires another distinctly named tool.
+3 -11
View File
@@ -126,16 +126,8 @@ export const Config: z<Config> = z.object({
model: z.string(),
}).default(undefined as unknown as { model: string }),
persona: z.string(),
// A schemastery object materializes {} (with [] for nested arrays) when the
// key is omitted — for toolFilter that would mean an EMPTY ALLOW-LIST, i.e.
// deny-everything, silently. Force the omitted key to stay absent (the same
// shape discipline as SystemPrompt's toolOrder); the cast is needed because
// .default() expects the object type.
// The NESTED arrays get the same treatment as the object itself: a partial
// filter ({deny: […]}) must not materialize allow: [] beside it — an empty
// allow-list means deny-EVERYTHING, so the materialized default would turn
// a deny-one config into deny-all. An EXPLICIT allow: [] (grant-only
// children) survives, since only the omitted key defaults to undefined.
// Schemastery otherwise materializes omitted objects and nested arrays as `{ allow: [] }`, which
// silently means deny all. Preserve omission while retaining an explicit empty allow-list.
toolFilter: z.object({
allow: z.array(z.string()).default(undefined as unknown as string[]),
deny: z.array(z.string()).default(undefined as unknown as string[]),
@@ -442,7 +434,7 @@ export function apply(ctx: Context, config: Config): void {
if (present !== undefined) {
mount(present)
} else {
// Not an error: the backend's fiber may simply activate after this one.
// Not an error: the backend's fiber may activate after this one.
// The tool appears the moment the provider registers; a typo'd provider
// name shows up as this note plus a tool that never materializes.
ctx.logger.info(`subagent provider "${config.provider}" not registered yet; the "${config.toolName ?? 'subagent'}" tool will register when it appears`)