From 744d65d63c4f39853b9c29071758d4a1e912b32a Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Thu, 23 Jul 2026 00:10:53 +0800 Subject: [PATCH] docs(i18n): refresh Agent Note translations after merge --- ...6-06-11-content-block-vocabulary.i18n.yaml | 4 +- .../2026-06-11-content-block-vocabulary.zh.md | 10 +- .../2026-06-11-custom-schema-dsl.i18n.yaml | 4 +- .../2026-06-11-custom-schema-dsl.zh.md | 2 +- ...ev-invariants-over-deep-readonly.i18n.yaml | 4 +- ...11-dev-invariants-over-deep-readonly.zh.md | 12 +- ...026-06-11-event-sourced-sessions.i18n.yaml | 4 +- .../2026-06-11-event-sourced-sessions.zh.md | 2 +- ...06-11-microkernel-event-taxonomy.i18n.yaml | 4 +- ...026-06-11-microkernel-event-taxonomy.zh.md | 8 +- ...026-06-11-runtime-arg-validation.i18n.yaml | 4 +- .../2026-06-11-runtime-arg-validation.zh.md | 4 +- ...-06-11-structured-error-taxonomy.i18n.yaml | 4 +- ...2026-06-11-structured-error-taxonomy.zh.md | 8 +- ...-tool-schemas-in-prompt-assembly.i18n.yaml | 4 +- ...6-11-tool-schemas-in-prompt-assembly.zh.md | 2 +- .../2026-06-13-capability-seams.i18n.yaml | 4 +- .../2026-06-13-capability-seams.zh.md | 12 +- .../2026-06-13-twin-llm-adapters.i18n.yaml | 4 +- .../2026-06-13-twin-llm-adapters.zh.md | 4 +- .../2026-06-14-session-persistence.i18n.yaml | 4 +- .../2026-06-14-session-persistence.zh.md | 14 +- ...6-06-15-turn-enclosure-invariant.i18n.yaml | 4 +- .../2026-06-15-turn-enclosure-invariant.zh.md | 6 +- ...06-17-filesystem-capability-seam.i18n.yaml | 4 +- ...026-06-17-filesystem-capability-seam.zh.md | 18 +-- ...nt-lifecycle-and-ownership-seams.i18n.yaml | 4 +- ...-18-agent-lifecycle-and-ownership-seams.md | 2 + ...-agent-lifecycle-and-ownership-seams.zh.md | 26 ++-- .../2026-06-18-session-surface.i18n.yaml | 4 +- .../2026-06-18-session-surface.md | 2 + .../2026-06-18-session-surface.zh.md | 24 ++-- ...ed-persistence-write-coordinator.i18n.yaml | 4 +- ...shared-persistence-write-coordinator.zh.md | 12 +- .../2026-06-20-branded-ids.i18n.yaml | 4 +- .../architecture/2026-06-20-branded-ids.md | 2 + .../architecture/2026-06-20-branded-ids.zh.md | 34 ++--- ...-20-extract-example-app-packages.i18n.yaml | 4 +- ...6-06-20-extract-example-app-packages.zh.md | 22 +-- ...eneric-long-running-tool-runtime.i18n.yaml | 4 +- ...06-20-generic-long-running-tool-runtime.md | 2 + ...20-generic-long-running-tool-runtime.zh.md | 135 ++++++++++++++---- .../2026-06-20-package-hierarchy.i18n.yaml | 4 +- .../2026-06-20-package-hierarchy.md | 2 + .../2026-06-20-package-hierarchy.zh.md | 4 +- ...andatory-app-attribution-headers.i18n.yaml | 4 +- ...21-mandatory-app-attribution-headers.zh.md | 20 +-- .../2026-06-24-web-capability-seam.i18n.yaml | 4 +- .../2026-06-24-web-capability-seam.md | 8 +- .../2026-06-24-web-capability-seam.zh.md | 16 ++- ...06-26-file-context-as-event-gate.i18n.yaml | 4 +- ...026-06-26-file-context-as-event-gate.zh.md | 30 ++-- ...stdin-env-trusted-plugin-surface.i18n.yaml | 4 +- ...ash-stdin-env-trusted-plugin-surface.zh.md | 12 +- ...026-06-30-event-domain-semantics.i18n.yaml | 4 +- .../2026-06-30-event-domain-semantics.zh.md | 10 +- .../2026-07-02-fs-per-session-cwd.i18n.yaml | 4 +- .../2026-07-02-fs-per-session-cwd.md | 2 + .../2026-07-02-fs-per-session-cwd.zh.md | 15 +- ...2-result-time-applied-hunk-diffs.i18n.yaml | 4 +- ...07-02-result-time-applied-hunk-diffs.zh.md | 8 +- ...6-07-02-tool-render-intent-union.i18n.yaml | 4 +- .../2026-07-02-tool-render-intent-union.zh.md | 10 +- ...ilesystem-directory-listing-seam.i18n.yaml | 4 +- ...03-filesystem-directory-listing-seam.zh.md | 2 +- ...bles-and-tool-guidance-ownership.i18n.yaml | 4 +- ...ariables-and-tool-guidance-ownership.zh.md | 24 ++-- ...6-07-05-reconstructable-requests.i18n.yaml | 4 +- .../2026-07-05-reconstructable-requests.zh.md | 29 ++-- ...bagent-provider-lifecycle-events.i18n.yaml | 4 +- ...5-subagent-provider-lifecycle-events.zh.md | 12 +- ...6-07-06-timeout-deadline-library.i18n.yaml | 4 +- .../2026-07-06-timeout-deadline-library.zh.md | 27 +++- ...6-07-07-tool-call-timeout-policy.i18n.yaml | 4 +- .../2026-07-07-tool-call-timeout-policy.zh.md | 18 +-- .../2026-07-08-agent-scope-contexts.i18n.yaml | 4 +- .../2026-07-08-agent-scope-contexts.zh.md | 11 +- ...07-12-agent-scope-runtime-design.i18n.yaml | 4 +- ...026-07-12-agent-scope-runtime-design.zh.md | 24 ++-- ...-06-14-acp-agent-client-protocol.i18n.yaml | 4 +- ...2026-06-14-acp-agent-client-protocol.zh.md | 4 +- .../2026-06-14-acp-multi-session.i18n.yaml | 4 +- .../2026-06-14-acp-multi-session.zh.md | 14 +- .../feature/2026-06-15-code-mode.i18n.yaml | 4 +- .../feature/2026-06-15-code-mode.zh.md | 30 ++-- ...26-06-17-filesystem-tool-schemas.i18n.yaml | 4 +- .../2026-06-17-filesystem-tool-schemas.zh.md | 10 +- ...-acp-terminal-and-tool-rendering.i18n.yaml | 4 +- ...6-18-acp-terminal-and-tool-rendering.zh.md | 6 +- ...06-18-compaction-capability-seam.i18n.yaml | 4 +- .../2026-06-18-compaction-capability-seam.md | 2 + ...026-06-18-compaction-capability-seam.zh.md | 80 ++++++----- ...6-06-21-subagent-capability-seam.i18n.yaml | 4 +- .../2026-06-21-subagent-capability-seam.md | 2 + .../2026-06-21-subagent-capability-seam.zh.md | 17 ++- .../2026-06-22-acp-subagent-backend.i18n.yaml | 4 +- .../2026-06-22-acp-subagent-backend.zh.md | 15 +- .../2026-06-25-ask-user-question.i18n.yaml | 4 +- .../2026-06-25-ask-user-question.zh.md | 10 +- .../2026-06-29-todo-write-tool.i18n.yaml | 4 +- .../feature/2026-06-29-todo-write-tool.zh.md | 6 +- .../feature/2026-06-30-hook-bridges.i18n.yaml | 4 +- .../feature/2026-06-30-hook-bridges.zh.md | 16 +-- .../2026-06-30-hook-protocol-lib.i18n.yaml | 4 +- .../2026-06-30-hook-protocol-lib.zh.md | 8 +- .../2026-06-30-interception-seams.i18n.yaml | 4 +- .../2026-06-30-interception-seams.zh.md | 22 +-- ...026-06-30-session-store-fork-api.i18n.yaml | 4 +- .../2026-06-30-session-store-fork-api.zh.md | 6 +- ...26-06-30-subagent-observe-enrich.i18n.yaml | 4 +- .../2026-06-30-subagent-observe-enrich.zh.md | 12 +- .../2026-07-05-dynamic-workflows.i18n.yaml | 4 +- .../2026-07-05-dynamic-workflows.zh.md | 8 +- .../feature/2026-07-05-skill-system.i18n.yaml | 4 +- .../feature/2026-07-05-skill-system.zh.md | 6 +- .../2026-07-06-approval-seam.i18n.yaml | 4 +- .../feature/2026-07-06-approval-seam.zh.md | 57 ++++---- .../2026-07-06-explicit-tool-order.i18n.yaml | 4 +- .../2026-07-06-explicit-tool-order.zh.md | 11 +- .../feature/2026-07-06-sandbox.i18n.yaml | 4 +- .../feature/2026-07-06-sandbox.zh.md | 53 ++++--- .../2026-07-07-mcp-client-plugin.i18n.yaml | 4 +- .../2026-07-07-mcp-client-plugin.zh.md | 22 +-- .../2026-07-07-session-prefix.i18n.yaml | 4 +- .../feature/2026-07-07-session-prefix.md | 2 + .../feature/2026-07-07-session-prefix.zh.md | 25 ++-- .../2026-07-08-repeat-tool-guard.i18n.yaml | 4 +- .../2026-07-08-repeat-tool-guard.zh.md | 18 +-- ...-self-referential-cordis-toolset.i18n.yaml | 4 +- ...7-08-self-referential-cordis-toolset.zh.md | 18 +-- ...2026-07-10-session-query-service.i18n.yaml | 4 +- .../2026-07-10-session-query-service.zh.md | 15 +- ...nt-persona-tool-filter-and-depth.i18n.yaml | 4 +- ...bagent-persona-tool-filter-and-depth.zh.md | 12 +- .../2026-06-11-doc-sync-enforcement.i18n.yaml | 4 +- .../2026-06-11-doc-sync-enforcement.zh.md | 10 +- .../2026-06-11-quality-gates.i18n.yaml | 4 +- .../process/2026-06-11-quality-gates.md | 2 + .../process/2026-06-11-quality-gates.zh.md | 12 +- .../2026-06-11-tsdown-over-dumble.i18n.yaml | 4 +- .../2026-06-11-tsdown-over-dumble.zh.md | 6 +- ...26-06-11-vendor-cordis-as-source.i18n.yaml | 4 +- .../2026-06-11-vendor-cordis-as-source.zh.md | 2 +- .../2026-06-16-pnpm-over-yarn.i18n.yaml | 4 +- .../process/2026-06-16-pnpm-over-yarn.zh.md | 6 +- .../2026-06-17-ts-build-config.i18n.yaml | 4 +- .../process/2026-06-17-ts-build-config.zh.md | 3 +- ...6-06-18-markdown-cross-link-lint.i18n.yaml | 4 +- .../2026-06-18-markdown-cross-link-lint.zh.md | 10 +- ...-06-20-agent-note-classification.i18n.yaml | 4 +- .../2026-06-20-agent-note-classification.md | 2 + ...2026-06-20-agent-note-classification.zh.md | 26 ++-- ...-20-core-data-structures-catalog.i18n.yaml | 4 +- ...6-06-20-core-data-structures-catalog.zh.md | 22 +-- ...6-06-20-generated-cordis-catalog.i18n.yaml | 4 +- .../2026-06-20-generated-cordis-catalog.zh.md | 16 +-- .../2026-07-02-tool-schema-catalog.i18n.yaml | 4 +- .../2026-07-02-tool-schema-catalog.zh.md | 10 +- ...-07-03-documentation-graph-atlas.i18n.yaml | 4 +- ...2026-07-03-documentation-graph-atlas.zh.md | 27 ++-- ...4-cordis-jsdoc-completeness-gate.i18n.yaml | 4 +- ...07-04-cordis-jsdoc-completeness-gate.zh.md | 12 +- ...2026-07-04-doc-tiers-and-budgets.i18n.yaml | 4 +- .../2026-07-04-doc-tiers-and-budgets.zh.md | 10 +- ...26-07-04-persistence-log-catalog.i18n.yaml | 4 +- .../2026-07-04-persistence-log-catalog.zh.md | 14 +- ...-07-05-uniform-agent-note-format.i18n.yaml | 4 +- .../2026-07-05-uniform-agent-note-format.md | 2 + ...2026-07-05-uniform-agent-note-format.zh.md | 22 +-- ...-07-06-export-surface-jsdoc-gate.i18n.yaml | 4 +- ...2026-07-06-export-surface-jsdoc-gate.zh.md | 6 +- ...6-07-06-generated-config-catalog.i18n.yaml | 4 +- .../2026-07-06-generated-config-catalog.zh.md | 6 +- .../2026-07-06-node-engine-floor.i18n.yaml | 4 +- .../2026-07-06-node-engine-floor.zh.md | 6 +- ...6-07-06-parallel-github-ci-gates.i18n.yaml | 4 +- .../2026-07-06-parallel-github-ci-gates.md | 2 + .../2026-07-06-parallel-github-ci-gates.zh.md | 45 +++--- ...26-07-06-parallel-pre-push-gates.i18n.yaml | 4 +- .../2026-07-06-parallel-pre-push-gates.md | 2 + .../2026-07-06-parallel-pre-push-gates.zh.md | 37 ++--- ...10-readme-known-limitations-gate.i18n.yaml | 4 +- ...-07-10-readme-known-limitations-gate.zh.md | 8 +- ...ackage-model-experience-contract.i18n.yaml | 4 +- ...07-12-package-model-experience-contract.md | 2 + ...12-package-model-experience-contract.zh.md | 18 +-- ...-19-drop-mutable-session-summary.i18n.yaml | 4 +- ...6-06-19-drop-mutable-session-summary.zh.md | 8 +- ...llapse-trace-only-session-events.i18n.yaml | 4 +- ...0-collapse-trace-only-session-events.zh.md | 4 +- ...onsumed-llm-adapter-change-event.i18n.yaml | 4 +- ...-unconsumed-llm-adapter-change-event.zh.md | 10 +- ...nconsumed-llm-assembled-surfaces.i18n.yaml | 4 +- ...op-unconsumed-llm-assembled-surfaces.zh.md | 6 +- ...26-06-20-prune-dead-seam-methods.i18n.yaml | 4 +- .../2026-06-20-prune-dead-seam-methods.md | 2 + .../2026-06-20-prune-dead-seam-methods.zh.md | 12 +- ...-06-20-public-agent-stop-surface.i18n.yaml | 4 +- ...2026-06-20-public-agent-stop-surface.zh.md | 12 +- ...ove-agent-boundary-mirror-events.i18n.yaml | 4 +- ...-20-remove-agent-boundary-mirror-events.md | 2 + ...-remove-agent-boundary-mirror-events.zh.md | 31 ++-- ...06-20-unify-agent-and-session-id.i18n.yaml | 4 +- .../2026-06-20-unify-agent-and-session-id.md | 2 + ...026-06-20-unify-agent-and-session-id.zh.md | 38 +++-- .../2026-06-26-fsspec-style-fs-seam.i18n.yaml | 4 +- .../2026-06-26-fsspec-style-fs-seam.zh.md | 20 +-- ...07-02-remove-stream-chunk-mirror.i18n.yaml | 4 +- ...026-07-02-remove-stream-chunk-mirror.zh.md | 12 +- ...6-07-04-drop-image-content-block.i18n.yaml | 4 +- .../2026-07-04-drop-image-content-block.zh.md | 6 +- ...6-07-04-drop-inert-request-knobs.i18n.yaml | 4 +- .../2026-07-04-drop-inert-request-knobs.zh.md | 8 +- ...consumed-web-observation-surface.i18n.yaml | 4 +- ...p-unconsumed-web-observation-surface.zh.md | 10 +- .../2026-07-04-fold-stdio-ui-helper.i18n.yaml | 4 +- .../2026-07-04-fold-stdio-ui-helper.md | 2 + .../2026-07-04-fold-stdio-ui-helper.zh.md | 10 +- ...producerless-vocabulary-variants.i18n.yaml | 4 +- ...-prune-producerless-vocabulary-variants.md | 2 + ...une-producerless-vocabulary-variants.zh.md | 14 +- ...7-04-prune-write-only-fs-surface.i18n.yaml | 4 +- ...26-07-04-prune-write-only-fs-surface.zh.md | 6 +- ...-04-remove-agent-steering-mirror.i18n.yaml | 4 +- ...6-07-04-remove-agent-steering-mirror.zh.md | 12 +- ...26-07-04-share-app-bin-boot-glue.i18n.yaml | 4 +- .../2026-07-04-share-app-bin-boot-glue.zh.md | 8 +- ...4-tighten-hook-protocol-contract.i18n.yaml | 4 +- ...07-04-tighten-hook-protocol-contract.zh.md | 8 +- ...m-acp-bridge-unreachable-surface.i18n.yaml | 4 +- ...-trim-acp-bridge-unreachable-surface.zh.md | 4 +- ...unconsumed-skill-provider-events.i18n.yaml | 4 +- ...rop-unconsumed-skill-provider-events.zh.md | 4 +- ...-12-prune-unused-web-seam-fields.i18n.yaml | 4 +- ...6-07-12-prune-unused-web-seam-fields.zh.md | 4 +- ...plify-session-log-representation.i18n.yaml | 4 +- ...-12-simplify-session-log-representation.md | 2 + ...-simplify-session-log-representation.zh.md | 27 ++-- ...026-06-11-property-based-testing.i18n.yaml | 4 +- .../2026-06-11-property-based-testing.zh.md | 6 +- .../2026-06-19-acp-snapshot-tests.i18n.yaml | 4 +- .../2026-06-19-acp-snapshot-tests.zh.md | 36 ++--- .../2026-06-19-real-api-e2e-ci.i18n.yaml | 4 +- .../testing/2026-06-19-real-api-e2e-ci.zh.md | 12 +- ...ant-snapshot-log-expected-output.i18n.yaml | 4 +- ...-redundant-snapshot-log-expected-output.md | 2 + ...dundant-snapshot-log-expected-output.zh.md | 16 +-- ...-fork-child-replay-seed-boundary.i18n.yaml | 4 +- ...6-22-fork-child-replay-seed-boundary.zh.md | 4 +- ...26-06-22-fork-snapshot-scenarios.i18n.yaml | 4 +- .../2026-06-22-fork-snapshot-scenarios.zh.md | 12 +- ...6-06-22-subagent-snapshot-replay.i18n.yaml | 4 +- .../2026-06-22-subagent-snapshot-replay.zh.md | 10 +- .../2026-07-04-hook-snapshot-matrix.i18n.yaml | 4 +- .../2026-07-04-hook-snapshot-matrix.zh.md | 18 +-- ...-single-source-acp-replay-config.i18n.yaml | 4 +- ...7-04-single-source-acp-replay-config.zh.md | 6 +- ...t-header-content-in-one-scenario.i18n.yaml | 4 +- ...quest-header-content-in-one-scenario.zh.md | 12 +- ...7-08-shared-acp-snapshot-package.i18n.yaml | 4 +- .../2026-07-08-shared-acp-snapshot-package.md | 2 + ...26-07-08-shared-acp-snapshot-package.zh.md | 20 +-- .../2026-06-16-typed-event-schemas.i18n.yaml | 4 +- .../2026-06-16-typed-event-schemas.zh.md | 18 +-- ...026-06-30-pre-tool-input-rewrite.i18n.yaml | 4 +- .../2026-06-30-pre-tool-input-rewrite.zh.md | 4 +- ...code-and-codex-subagent-backends.i18n.yaml | 4 +- ...ude-code-and-codex-subagent-backends.zh.md | 28 ++-- ...-07-08-interactive-side-sessions.i18n.yaml | 4 +- ...2026-07-08-interactive-side-sessions.zh.md | 6 +- ...10-sqlite-session-query-provider.i18n.yaml | 4 +- ...-07-10-sqlite-session-query-provider.zh.md | 10 +- ...flow-progress-through-tool-calls.i18n.yaml | 4 +- ...workflow-progress-through-tool-calls.zh.md | 2 +- ...2026-06-11-api-extractor-reports.i18n.yaml | 4 +- .../2026-06-11-api-extractor-reports.md | 2 + .../2026-06-11-api-extractor-reports.zh.md | 4 +- ...-06-11-architectural-conformance.i18n.yaml | 4 +- ...2026-06-11-architectural-conformance.zh.md | 10 +- ...11-supply-chain-and-vendor-drift.i18n.yaml | 4 +- ...-06-11-supply-chain-and-vendor-drift.zh.md | 8 +- ...06-20-discover-package-inventory.i18n.yaml | 4 +- ...026-06-20-discover-package-inventory.zh.md | 13 +- ...04-prune-dead-core-spine-surface.i18n.yaml | 4 +- ...-07-04-prune-dead-core-spine-surface.zh.md | 23 +-- ...deterministic-and-stress-testing.i18n.yaml | 4 +- ...-11-deterministic-and-stress-testing.zh.md | 8 +- .../2026-06-11-mutation-testing.i18n.yaml | 4 +- .../testing/2026-06-11-mutation-testing.zh.md | 10 +- ...-06-11-immutable-public-surfaces.i18n.yaml | 4 +- ...2026-06-11-immutable-public-surfaces.zh.md | 4 +- ...-06-20-providerless-example-base.i18n.yaml | 4 +- ...2026-06-20-providerless-example-base.zh.md | 6 +- ...generate-agent-note-index-tables.i18n.yaml | 4 +- ...-07-04-generate-agent-note-index-tables.md | 2 + ...-04-generate-agent-note-index-tables.zh.md | 34 +++-- ...ssembled-assistant-messages-only.i18n.yaml | 4 +- ...20-assembled-assistant-messages-only.zh.md | 16 +-- ...2026-06-20-drop-acp-session-load.i18n.yaml | 4 +- .../2026-06-20-drop-acp-session-load.zh.md | 10 +- ...026-06-20-drop-acp-terminal-meta.i18n.yaml | 4 +- .../2026-06-20-drop-acp-terminal-meta.zh.md | 8 +- ...-20-drop-bash-output-spill-files.i18n.yaml | 4 +- ...6-06-20-drop-bash-output-spill-files.zh.md | 8 +- ...-20-drop-durable-step-boundaries.i18n.yaml | 4 +- ...6-06-20-drop-durable-step-boundaries.zh.md | 10 +- ...6-20-drop-unused-session-lineage.i18n.yaml | 4 +- ...26-06-20-drop-unused-session-lineage.zh.md | 6 +- ...ld-session-persistence-interface.i18n.yaml | 4 +- ...0-fold-session-persistence-interface.zh.md | 4 +- ...026-06-20-generic-tool-rendering.i18n.yaml | 4 +- .../2026-06-20-generic-tool-rendering.zh.md | 6 +- ...6-06-20-retire-mid-turn-steering.i18n.yaml | 4 +- .../2026-06-20-retire-mid-turn-steering.zh.md | 6 +- ...-06-20-single-session-acp-bridge.i18n.yaml | 4 +- ...2026-06-20-single-session-acp-bridge.zh.md | 8 +- ...06-20-truncate-interrupted-turns.i18n.yaml | 4 +- ...026-06-20-truncate-interrupted-turns.zh.md | 8 +- ...nimplemented-subagent-vocabulary.i18n.yaml | 4 +- ...prune-unimplemented-subagent-vocabulary.md | 2 + ...ne-unimplemented-subagent-vocabulary.zh.md | 18 +-- ...apse-workflow-to-foreground-core.i18n.yaml | 4 +- ...collapse-workflow-to-foreground-core.zh.md | 8 +- ...ne-unused-skill-registry-surface.i18n.yaml | 4 +- ...-prune-unused-skill-registry-surface.zh.md | 8 +- 325 files changed, 1535 insertions(+), 1321 deletions(-) diff --git a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml index fb029f04fe..cc77840694 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.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 -2026-06-11-content-block-vocabulary.md: 6a90813ec90c6522a88a09d3536ce0cef8250238 -2026-06-11-content-block-vocabulary.zh.md: c9c65ae7e556cdf4eced0d45051862ff87fcd0fe +2026-06-11-content-block-vocabulary.md: 9aad01cee6083b1f380be66869af3137a07d9f1f +2026-06-11-content-block-vocabulary.zh.md: 123ca87f55be5129855a330efbfe0818d7cdbc0c diff --git a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md index c9c65ae7e5..123ca87f55 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md @@ -1,4 +1,4 @@ -# RFC: 由 dsh-llm 拥有的提供方无关内容块词汇 +# Agent Note: 由 dsh-llm 拥有的提供方无关内容块词汇 Status: implemented @@ -12,7 +12,7 @@ harness 需要一套统一的内部消息语言,供 agent loop(智能体循 自主拥有词汇:消息是类型化内容块的数组(`text`、`reasoning`、`tool-call`、`tool-result`),其联合类型派生自可合并扩展的 `ContentBlockMap`,插件通过声明合并添加新的块类型。同一可合并扩展映射模式为所有「字符串化」字段提供类型(`MessageSource`、`FinishReason`、`TurnTrigger`、`TurnEndReason`)。流式输出采用原始分片协议;`BlockAssembler` 是唯一的共享组装实现。适配器负责转换为提供方的协议格式(wire format)——映射成本留在适配器中,正是它该在的地方。 -会话内上下文注入(`context/message`、`steering/message`)渲染为带标签的 user-role 信封(system-reminder 模式),而非引入新角色,因此适配器无需承担额外负担。实际适配器验证已确认此渲染方式符合当前 DeepSeek 的行为;如果未来某提供方出现不兼容,应在该适配器内处理,而非引入新的规范角色。 +会话内上下文注入(`context/message`)和轮次中途 steering(`steering/message`)最初渲染为带标签的 user-role 信封(system-reminder 模式),而非引入新角色,因此适配器无需承担额外负担。如今两者都投影为无包装的普通用户内容;见[注入内容信封 Agent Note](../simplification/2026-07-20-unwrap-injected-content-envelopes.md)。实际适配器验证已确认此渲染方式符合当前 DeepSeek 的行为;如果未来某提供方出现不兼容,应在该适配器内处理,而非引入新的规范角色。 ## 曾考虑的替代方案 @@ -22,7 +22,7 @@ harness 需要一套统一的内部消息语言,供 agent loop(智能体循 ## 后果 - 推理(reasoning)在核心层有了归属,无需依赖提供方特有的结构。 -- 多模态块只有在适配器、UI 和上下文压缩(context compaction)三方协同支持后才会回归;见 [drop-image RFC](../simplification/2026-07-04-drop-image-content-block.md)。 -- 缓存提示与 assistant prefill 在有实际适配器能兑现之前保持缺席;见 [producer-less variants](../simplification/2026-07-04-prune-producerless-vocabulary-variants.md) 与 [inert request knobs](../simplification/2026-07-04-drop-inert-request-knobs.md) RFC。 +- 多模态块只有在适配器、UI 和上下文压缩(context compaction)三方协同支持后才会回归;见 [drop-image Agent Note](../simplification/2026-07-04-drop-image-content-block.md)。 +- 缓存提示与 assistant prefill 在有实际适配器能兑现之前保持缺席;见 [producer-less variants](../simplification/2026-07-04-prune-producerless-vocabulary-variants.md) 与 [inert request knobs](../simplification/2026-07-04-drop-inert-request-knobs.md) Agent Note。 - 每个适配器都需承担翻译成本;首批真实适配器已验证了流式输出协议,新适配器应继续在适配器本地测试中验证其提供方特有的映射。 -- 跨包(package)边界的 ID 使用品牌类型(`CallId`、`SessionId`、`AgentId`)——零运行时开销的名义类型。 +- 跨包(package)边界的 ID 使用品牌类型(`CallId`、agent 与 session 共享的 `SessionId`)——零运行时开销的名义类型。 diff --git a/.agents/notes/implemented/architecture/2026-06-11-custom-schema-dsl.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-11-custom-schema-dsl.i18n.yaml index c08b4133de..cf2b48bfdb 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-custom-schema-dsl.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-11-custom-schema-dsl.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 -2026-06-11-custom-schema-dsl.md: 34c018b779d45c5eadb7337cb060c43b6f8d09b1 -2026-06-11-custom-schema-dsl.zh.md: 674a6b1a0b67617ffb4ab919899aa108c646a489 +2026-06-11-custom-schema-dsl.md: 9cef4175e4c4a2e6d2ea034d3522d8a8ddd1f5cb +2026-06-11-custom-schema-dsl.zh.md: 8b1241edea4c591e428fbaa15916437a3de42061 diff --git a/.agents/notes/implemented/architecture/2026-06-11-custom-schema-dsl.zh.md b/.agents/notes/implemented/architecture/2026-06-11-custom-schema-dsl.zh.md index 674a6b1a0b..8b1241edea 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-custom-schema-dsl.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-11-custom-schema-dsl.zh.md @@ -1,4 +1,4 @@ -# RFC: 使用自定义类型化 tool-schema DSL 替代 schemastery +# Agent Note: 使用自定义类型化 tool-schema DSL 替代 schemastery Status: implemented diff --git a/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.i18n.yaml index 2df4576e59..a3029832c5 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.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 -2026-06-11-dev-invariants-over-deep-readonly.md: 01a9e45fac77d2513924e78566a44a6059dd9d28 -2026-06-11-dev-invariants-over-deep-readonly.zh.md: 8f8db9f5bb095b8fc9c600b741c16d630a6c8167 +2026-06-11-dev-invariants-over-deep-readonly.md: 8f0e79f15af82ce3125b1f6f767d4ea727aa6d29 +2026-06-11-dev-invariants-over-deep-readonly.zh.md: 9998ddd784f956e3497a0bb9d8f5096047f14a70 diff --git a/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.zh.md b/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.zh.md index 8f8db9f5bb..9998ddd784 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.zh.md @@ -1,4 +1,4 @@ -# RFC: 源端拥有的会话不可变性与开发模式不变式 +# Agent Note: 源端拥有的会话不可变性与开发模式不变式 Status: implemented @@ -30,11 +30,11 @@ TypeScript readonly 类型不是充分的运行时边界。它们在程序运行 `deriveMessages()` 将已记录的表面事件投影为分离的、深度冻结的 `Message` 对象,并返回一份新的数组快照。因此请求组装可以将派生历史与其他输入组合,而不会暴露一条回到日志的路径。缓存复用安全的不可变投影,而非为每次模型调用重新克隆完整历史。 -### 不变式插件检查关系 +### 包拥有的不变式配套插件检查关系 -`dsh-invariants` 是一个纯监听器的开发插件。它不冻结记录,没有配置;dispose(资源释放)仅移除其断言。它检查需要跟踪状态或观察另一个 seam 的规则,包括单调递增的序列号、轮次与步骤嵌套、工具调用/结果配对、合法的 agent(智能体)状态转换、主体正确的作用域分发,以及循环构建的请求与从其会话日志前缀重建的请求之间的等价性。 +`dsh-invariants` 注册可配置的 `ctx.invariants` 服务,本身不包含产品检查。每个包发布一个 `./invariant` 所有权配套插件;`dsh-session`、`dsh-agent`、`dsh-scope` 和 `dsh-agent-loop` 目前添加需要 trace 状态或观察另一个 seam 的规则:单调递增的序列号、轮次与步骤嵌套、工具调用/结果配对、合法的 agent(智能体)状态转换、主体正确的作用域分发,以及循环构建的请求与从其会话日志前缀重建的请求之间的等价性。全局启用和包名 regex 过滤器归该服务所有(见[包拥有的不变式服务](2026-07-19-package-owned-invariant-service.md))。 -当插件附加到已有或已播种的会话时,它回放不可变日志以重建跟踪状态。这使得在轮次中途热重载是安全的,同时不赋予插件对会话存储的所有权。 +当 session 配套插件附加到已有或已播种的会话时,它回放不可变日志以重建跟踪状态。服务为每项贡献提供一个可 dispose(资源释放)的子 fiber,因此轮次中途热重载是安全的,同时不赋予诊断逻辑对会话存储的所有权。 ## 曾考虑的替代方案 @@ -55,6 +55,6 @@ TypeScript readonly 类型不是充分的运行时边界。它们在程序运行 - 每个被接受的实时或种子会话事件在任何观察者接收之前,都已从调用方拥有的输入中分离并深度不可变。 - `session.events` 暴露稳定的不可变快照,而非私有的增长数组。 - 请求侧的修改无法通过派生消息触及已存储的历史。 -- 开发构建可以启用关系断言而不改变存储行为;dispose 或省略该插件不会削弱日志不可变性。 -- `dsh-invariants` 没有 `Config` 表面,因为它没有可调节的行为。 +- 开发构建可以启用关系断言而不改变存储行为;dispose 或过滤一个配套插件不会削弱日志不可变性。 +- `dsh-invariants` 配置全局启用状态以及包 allow/block regex 列表;每项检查仍由其产品包拥有并测试。 - 运行时边界对每个被接受的事件产生一次递归快照与冻结的开销;后续读取者和缓存投影复用已拥有的不可变记录。 diff --git a/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.i18n.yaml index 747bbf3ab0..3ea1c711c4 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.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 -2026-06-11-event-sourced-sessions.md: c1460b84c5928a53537a5f6a51f41c4d5b101a12 -2026-06-11-event-sourced-sessions.zh.md: 938404524cd23a5a5b3d6d0b7fd5020df3d35a51 +2026-06-11-event-sourced-sessions.md: 15ba7b23d5eae48e7dee2328b5924493d54aeeb0 +2026-06-11-event-sourced-sessions.zh.md: fc900b0dc43b18ba2016b4dd57584cf15707b016 diff --git a/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.zh.md b/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.zh.md index 938404524c..fc900b0dc4 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.zh.md @@ -1,4 +1,4 @@ -# RFC: 事件溯源的会话与派生消息历史 +# Agent Note: 事件溯源的会话与派生消息历史 Status: implemented diff --git a/.agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.i18n.yaml index 107aab3c75..a15ebcfddf 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.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 -2026-06-11-microkernel-event-taxonomy.md: 47e363f949b506756d9b20ee7dc53ca81e3f5e4d -2026-06-11-microkernel-event-taxonomy.zh.md: 3b0aa54108bf8737e6fe341c7b0797a554d328ec +2026-06-11-microkernel-event-taxonomy.md: 8bf05b7deba5f054d4ec8ecf104c3b8798e42d4e +2026-06-11-microkernel-event-taxonomy.zh.md: 4ff2ab632ca02e98137a15f19a7996a740a519b0 diff --git a/.agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.zh.md b/.agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.zh.md index 3b0aa54108..4ff2ab632c 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.zh.md @@ -1,4 +1,4 @@ -# RFC: 微内核——通过 Cordis 事件分类体系实现扩展,唯一具体循环 +# Agent Note: 微内核——通过 Cordis 事件分类体系实现扩展,唯一具体循环 Status: implemented @@ -12,8 +12,8 @@ Status: implemented 纯 Cordis 事件分类体系。agent loop(智能体循环)的扩展 seam 是带类型的事件,具有明确的分发模式: -- **waterfall(瀑布式事件)**(around-middleware):插件可变换、否决或包装:`agent/prompt-submit`、`agent/request`、`agent/step-result`、`agent/turn-continuation`、`tools/pre-execute`、`tools/execute`、`tools/post-execute`、`llm/stream`、`system-prompt/assemble`。 -- **serial**(按监听器顺序依次 await;bail 值会阻止后续监听器执行):用于有序检查点。所有 `agent/pre-step` 监听器在全部弃权时才继续运行,而 `agent/turn-stop` 返回的第一个 stop 值即为最终的终止决策。 +- **waterfall(瀑布式事件)**(around-middleware):插件可变换、否决、恢复或包装:`agent/prompt-submit`、`agent/request`、`agent/request-error`、`agent/step-result`、`agent/turn-continuation`、`tools/pre-execute`、`tools/execute`、`tools/post-execute`、`llm/stream`、`system-prompt/assemble`。 +- **serial**(按监听器顺序依次 await;bail 值会阻止后续监听器执行):用于有序检查点。所有 `agent/pre-step` 和 `agent/post-step` 监听器在全部弃权时才继续运行,而 `agent/turn-stop` 返回的第一个 stop 值即为最终的终止决策。 - **parallel**(await 扇出):每个监听器都必须获得独立执行的机会:`session/flush` 持久性检查点。 - **emit**(同步 fire-and-forget):用于通知:轮次/步骤边界、流分片、生命周期、错误,以及包含不可变 `tools/result` 观测的事件。 @@ -25,7 +25,7 @@ Status: implemented ## 后果 -- 每个 MVP 功能都映射到一个监听器([功能→机制映射](../../../cookbook/extension-cookbook.md#the-feature--mechanism-map)是证明义务,保持更新)。 +- 每个 MVP 功能都映射到一个监听器([功能→机制映射](../../../../docs/cookbook/extension-cookbook.md#the-feature--mechanism-map)是证明义务,保持更新)。 - HMR 与 dispose 无需额外工作:监听器和注册均为 Cordis effect。 - waterfall 语义(调用 `next()` 或短路)不直观,需要教学——在 AGENTS.md 中记录,并由组合测试覆盖。 - 循环必须具备防御性:插件异常在轮次级别被隔离,任何 seam 发出的 steering(中途引导)永远不会被搁置(有回归测试保障)。 diff --git a/.agents/notes/implemented/architecture/2026-06-11-runtime-arg-validation.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-11-runtime-arg-validation.i18n.yaml index 0fa22e5594..46b8dad11a 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-runtime-arg-validation.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-11-runtime-arg-validation.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 -2026-06-11-runtime-arg-validation.md: 82d01263225ce719e80631d1890f0a6cc328119d -2026-06-11-runtime-arg-validation.zh.md: 4bd6720f7ec87fcef028ba94f445a5210f1b0d7a +2026-06-11-runtime-arg-validation.md: 6ac2a2ab475c9813dbfbb09e45da71821489d6cd +2026-06-11-runtime-arg-validation.zh.md: 119f738dbb3aaee4786aa58d5495ab5e012e206e diff --git a/.agents/notes/implemented/architecture/2026-06-11-runtime-arg-validation.zh.md b/.agents/notes/implemented/architecture/2026-06-11-runtime-arg-validation.zh.md index 4bd6720f7e..119f738dbb 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-runtime-arg-validation.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-11-runtime-arg-validation.zh.md @@ -1,4 +1,4 @@ -# RFC: 模型边界处的运行时参数校验 +# Agent Note: 模型边界处的运行时参数校验 Status: implemented @@ -21,4 +21,4 @@ Status: implemented - `ToolArgsError` 目前是带 `code` 字段的普通 `Error`;如果日后引入 harness 级别的错误分类体系,它将变为子类,但不影响读取 `.message` 的调用方。 - 校验开销相对于一次模型调用可忽略不计。 - + diff --git a/.agents/notes/implemented/architecture/2026-06-11-structured-error-taxonomy.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-11-structured-error-taxonomy.i18n.yaml index 4a381ba271..b886be0e38 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-structured-error-taxonomy.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-11-structured-error-taxonomy.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 -2026-06-11-structured-error-taxonomy.md: 5a5f75038b6124457d2d4d08cbe0bec80415a387 -2026-06-11-structured-error-taxonomy.zh.md: d5a45a447ca00d48cb8366911a10f34c584d915c +2026-06-11-structured-error-taxonomy.md: 9122193b3d01cf5a4c315e6f7a7218153fd4a60a +2026-06-11-structured-error-taxonomy.zh.md: cb29503314edf248c90f2eecd54e01f5b203b99c diff --git a/.agents/notes/implemented/architecture/2026-06-11-structured-error-taxonomy.zh.md b/.agents/notes/implemented/architecture/2026-06-11-structured-error-taxonomy.zh.md index d5a45a447c..cb29503314 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-structured-error-taxonomy.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-11-structured-error-taxonomy.zh.md @@ -1,4 +1,4 @@ -# RFC: 结构化错误分类体系 +# Agent Note: 结构化错误分类体系 Status: implemented @@ -12,7 +12,7 @@ Status: implemented 在 `dsh-llm`(叶子包,所有其他包都已依赖它,不引入新的依赖边)中引入一个 `HarnessError extends Error` 基类:稳定的 `code`(与 `message` 分离)、通过 `ErrorOptions` 进行 `cause` 链接、`name` 默认为子类名。`isHarnessError` 在 seam 处做类型收窄。 -- `LlmError`、`ToolArgsError`(dsh-tools)和 `InvariantError`(dsh-invariants)现在继承该基类,保留各自既有的 code。 +- `LlmError` 和 `ToolArgsError`(dsh-tools)继承该基类,保留各自既有的 code。 - `ToolExecutionResult` 新增可选字段 `error: { name, code }`,在注册表的 catch 中当抛出值为 `HarnessError` 时填充。agent loop 将其转发到 `tool/result` 会话事件(该事件也新增了同一可选字段),使结构化的失败信息存活到日志中,供重试/沙箱插件和回放使用。面向模型的文本块保持不变。 - agent loop 的 `toError` 将非 Error 的 throw 包装为 `HarnessError`(`code: 'UNKNOWN'`,原始值作为 `cause` 链接),而非裸 `Error`;这样即使是不规范的 throw 也能携带可路由的 code 进入会话的 `error` 事件(该事件此前已暴露 `code`)。 @@ -21,6 +21,6 @@ Status: implemented - 错误端到端可机器路由:插件可以基于 `error.code` 分支,而无需对 message 做子串匹配。 - 一个基类被广泛导入,但它位于所有包已经依赖的包中,代价仅是一条 import 语句,而非新的依赖边。 - `deriveMessages` 不会将 `error` 暴露到模型历史中——模型仍然看到文本块;结构化字段服务于代码和回放。 -- 参数校验与开发不变式保留各自既有的 code 和行为;共享基类增加了跨 seam 的路由元数据,不改变面向模型的文本。 +- 参数校验保留其既有的 code 和行为;包自有的诊断不变式独立携带稳定 code,使不变式注册表无需导入产品包。共享基类增加了跨 seam 的路由元数据,不改变面向模型的文本。 - + diff --git a/.agents/notes/implemented/architecture/2026-06-11-tool-schemas-in-prompt-assembly.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-11-tool-schemas-in-prompt-assembly.i18n.yaml index 57fe989a5d..61b72fe101 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-tool-schemas-in-prompt-assembly.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-11-tool-schemas-in-prompt-assembly.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 -2026-06-11-tool-schemas-in-prompt-assembly.md: 260d56ffab054d874e13eb34eac029bf59b381fc -2026-06-11-tool-schemas-in-prompt-assembly.zh.md: 5ae98dfac676574d00f34580ec079bb6a8f2331f +2026-06-11-tool-schemas-in-prompt-assembly.md: 3643ac3d61be08f629ef0cd0424fef5cb9696c3a +2026-06-11-tool-schemas-in-prompt-assembly.zh.md: 645b829627674e5ccd1507002309b7e8364ac59f diff --git a/.agents/notes/implemented/architecture/2026-06-11-tool-schemas-in-prompt-assembly.zh.md b/.agents/notes/implemented/architecture/2026-06-11-tool-schemas-in-prompt-assembly.zh.md index 5ae98dfac6..645b829627 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-tool-schemas-in-prompt-assembly.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-11-tool-schemas-in-prompt-assembly.zh.md @@ -1,4 +1,4 @@ -# RFC: 工具 schema 是系统提示词组装的一部分 +# Agent Note: 工具 schema 是系统提示词组装的一部分 Status: implemented diff --git a/.agents/notes/implemented/architecture/2026-06-13-capability-seams.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-13-capability-seams.i18n.yaml index fcdc63390f..3cdeabed71 100644 --- a/.agents/notes/implemented/architecture/2026-06-13-capability-seams.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-13-capability-seams.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 -2026-06-13-capability-seams.md: f10182737347fc54ff3fd06edb299e569397773d -2026-06-13-capability-seams.zh.md: e65d2f31eb80ce80e92b3e4bbb4d5b8948185c44 +2026-06-13-capability-seams.md: 7c755dced7825d2831acc0901f6412b8e5afe95a +2026-06-13-capability-seams.zh.md: f3aa1ebdee3a16bf095f317b86d01e00b6bbf0c7 diff --git a/.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md b/.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md index e65d2f31eb..f3aa1ebdee 100644 --- a/.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md @@ -1,4 +1,4 @@ -# RFC: 能力 seam——接口/实现/消费方三分 +# Agent Note: 能力 seam——接口/实现/消费方三分 Status: implemented @@ -8,15 +8,15 @@ Status: implemented harness 具有可替换的能力:当前是 bash 执行,未来会有沙箱化/远程执行器和替代模型提供方。一项能力涉及三个关注点,它们以不同速率、因不同原因变化:*契约*(这项能力是什么)、*实现*(它如何运行)、*消费方接口*(模型和其他插件面向什么编程)。将三者捆绑在一个包(package)中会耦合这些变化速率——把本地执行器换成沙箱化执行器时,模型看到的工具 schema 也会被搅动,尽管面向模型的契约从未改变。 -这与「谁在运行时提供、谁需要一项能力」是不同的问题,后者 Cordis 已通过 service + `inject` 解决(提供方注册 `ctx.bash`;消费方声明 `inject: ['bash']`,其 fiber 挂起直到服务存在)。该机制是必要的,但不决定包的边界;本 RFC 决定的是包的边界。 +这与「谁在运行时提供、谁需要一项能力」是不同的问题,后者 Cordis 已通过 service + `inject` 解决(提供方注册 `ctx.bash`;消费方声明 `inject: ['bash']`,其 fiber 挂起直到服务存在)。该机制是必要的,但不决定包的边界;本 Agent Note 决定的是包的边界。 ## 决策 一项可替换的能力由**三个包**构成: -1. **接口**——一个抽象服务加词汇类型,拥有 `ctx.`,仅依赖 cordis(例如 `dsh-bash`:`BashExecutor`、`BashRunResult`、`BashTask`)。 +1. **接口**——一个抽象服务加词汇类型,拥有 `ctx.`,仅依赖其词汇依赖(例如 `dsh-bash`:`BashExecutor`、`BashRunResult`、`BashProcess`)。 2. **实现**——一个具体子类,以插件形式加载(例如 `dsh-bash-local`:子进程、进程组 kill、溢出文件截断)。沙箱化/远程后端是实现同一接口的兄弟包。 -3. **消费方**——模型和插件看到的内容(例如 `dsh-tool-bash`:`bash`/`bash_output`/`bash_kill` 工具 schema)。消费方 `inject` 接口键,从不导入实现类型。 +3. **消费方**——模型和插件看到的内容(例如 `dsh-tool-bash`:`bash` schema,后台句柄注册到通用任务运行时)。消费方 `inject` 接口键,从不导入实现类型。 实现与消费方由此独立演进:沙箱化执行器替换 `dsh-bash-local` 时无需触碰任何工具 schema。 @@ -25,8 +25,8 @@ harness 具有可替换的能力:当前是 bash 执行,未来会有沙箱化 ## 曾考虑的替代方案 - **单一合并包**:否决。因为它重新耦合了三分设计本要分离的三种变化速率(这正是拆分的意义所在)。 -- **`@cordisjs/plugin-capability`**:这是完全不同的维度。它是一个权限/能力*安全*服务(具名权限加继承,通过 `ctx.capability.test` 对会话进行检测),是延后的权限/沙箱工作(`tools/pre-execute` deny/ask seam)的候选方案,不是替换实现的机制。混淆这两个「能力」概念正是本 RFC 所指出的陷阱。 +- **`@cordisjs/plugin-capability`**:这是完全不同的维度。它是一个权限/能力*安全*服务(具名权限加继承,通过 `ctx.capability.test` 对会话进行检测),是延后的权限/沙箱工作(`tools/pre-execute` deny/ask seam)的候选方案,不是替换实现的机制。混淆这两个「能力」概念正是本 Agent Note 所指出的陷阱。 ## 后果 -每项能力需要更多包和更多样板代码(一组 `package.json`/`tsconfig`/README,加上 inject 接线)。换来的是:实现与消费方独立发布和版本管理,新后端永远不会波及面向模型的契约。该规则记录在 [AGENTS.md](../../../../AGENTS.md) § Conventions("Capability seams are three packages")和 [architecture.md](../../../architecture.md) § "Capability seams" 中;bash 三件套是参考模板。何时合并、何时拆分是一个判断问题,架构文档对此有详细说明——本 RFC 记录的是*为什么*默认选择拆分。 +每项能力需要更多包和更多样板代码(一组 `package.json`/`tsconfig`/README,加上 inject 接线)。换来的是:实现与消费方独立发布和版本管理,新后端永远不会波及面向模型的契约。该规则记录在 [AGENTS.md](../../../../AGENTS.md) § Conventions(「Capability seams are three packages」)和 [architecture.md](../../../../docs/architecture.md) §「Capability seams」中;bash 三件套是参考模板。何时合并、何时拆分是一个判断问题,架构文档对此有详细说明——本 Agent Note 记录的是*为什么*默认选择拆分。 diff --git a/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.i18n.yaml index 545aae3758..f1d2fe1a90 100644 --- a/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.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 -2026-06-13-twin-llm-adapters.md: edd7080e2a16e9f1af2ebd56a4265040dc970c17 -2026-06-13-twin-llm-adapters.zh.md: 2087d498f65326fa0a2cb74be409a926a5e343f5 +2026-06-13-twin-llm-adapters.md: 5c3308b281ce71407002e95dd6e794da2a421fa8 +2026-06-13-twin-llm-adapters.zh.md: 93b084973bccaeb802508e4e939a259c281f2608 diff --git a/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.zh.md b/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.zh.md index 2087d498f6..93b084973b 100644 --- a/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.zh.md @@ -1,4 +1,4 @@ -# RFC: 以两个 LLM 适配器作为设计验证孪生体 +# Agent Note: 以两个 LLM 适配器作为设计验证孪生体 Status: implemented @@ -24,4 +24,4 @@ Status: implemented ## 后果 -孪生体使适配器和需要密钥的 e2e 维护量翻倍——两者都覆盖 V4 Flash 和 Pro 在各代表性推理(reasoning)模式下的行为——换来的是持续的 seam 中立性验证和第二份实现示例。两个适配器均使用 `apiKey`、`baseURL` 和 `models`;手写适配器暴露 `thinking`/`reasoningEffort`,pi-ai 适配器暴露一个 `reasoning` 级别。未来如果有一致性测试套件,可以通过后续 RFC 论证退役其中一个适配器。 +孪生体使适配器和需要密钥的 e2e 维护量翻倍——两者都覆盖 V4 Flash 和 Pro 在各代表性推理(reasoning)模式下的行为——换来的是持续的 seam 中立性验证和第二份实现示例。两个适配器均使用 `apiKey`、`baseURL` 和 `models`;手写适配器暴露 `thinking`/`reasoningEffort`,pi-ai 适配器暴露一个 `reasoning` 级别。未来如果有一致性测试套件,可以通过后续 Agent Note 论证退役其中一个适配器。 diff --git a/.agents/notes/implemented/architecture/2026-06-14-session-persistence.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-14-session-persistence.i18n.yaml index aa7fe88f24..35b81801d0 100644 --- a/.agents/notes/implemented/architecture/2026-06-14-session-persistence.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-14-session-persistence.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 -2026-06-14-session-persistence.md: a50c076746981892239304266194d1e8e309574f -2026-06-14-session-persistence.zh.md: d609e663bd9e36f74bf34404c021a30f54d3bd76 +2026-06-14-session-persistence.md: 1122a52471c6279eff7454cfd31692f05a7bba76 +2026-06-14-session-persistence.zh.md: 0f2bcd4948a3c95cd0497cfa63ef4454630635c7 diff --git a/.agents/notes/implemented/architecture/2026-06-14-session-persistence.zh.md b/.agents/notes/implemented/architecture/2026-06-14-session-persistence.zh.md index d609e663bd..0f2bcd4948 100644 --- a/.agents/notes/implemented/architecture/2026-06-14-session-persistence.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-14-session-persistence.zh.md @@ -1,4 +1,4 @@ -# RFC: 会话持久化作为基于现有 `SessionEvent` 的抽象服务 +# Agent Note: 会话持久化作为基于现有 `SessionEvent` 的抽象服务 Status: implemented @@ -6,24 +6,24 @@ Status: implemented ## 问题 -会话此前仅存在于内存中。示例插件 `session-jsonl.ts`(在两个示例中逐字节重复)是只写的遥测:它缓冲 `session/event` 并追加 JSON 行,没有读取/回放路径,没有崩溃安全性(无 fsync、无原子写入、fire-and-forget 的 dispose 排空),没有列表功能,也没有格式版本控制。没有任何机制能将磁盘上的历史会话重新注入到活跃的 agent(智能体)中,因此持久恢复("继续昨天的任务")、持久 fork 以及 ACP(Agent Client Protocol)的 `session/load` 方法([ACP 支持](../../implemented/feature/2026-06-14-acp-agent-client-protocol.md))都无法实现。 +会话此前仅存在于内存中。示例插件 `session-jsonl.ts`(在两个示例中逐字节重复)是只写的遥测:它缓冲 `session/event` 并追加 JSON 行,没有读取/回放路径,没有崩溃安全性(无 fsync、无原子写入、fire-and-forget 的 dispose 排空),没有列表功能,也没有格式版本控制。没有任何机制能将磁盘上的历史会话重新注入到活跃的 agent(智能体)中,因此持久恢复(「继续昨天的任务」)、持久 fork 以及 ACP(Agent Client Protocol)的 `session/load` 方法([ACP 支持](../feature/2026-06-14-acp-agent-client-protocol.md))都无法实现。 -[事件溯源模型](2026-06-11-event-sourced-sessions.md)将仅追加日志作为唯一真源,并从中派生 LLM(大语言模型)历史。持久化必须忠实于这一设计:直接持久化现有的 `SessionEvent`,不引入需要来回转换的并行"持久化消息"类型。后端也必须可替换——当前用文件存储,以后用数据库存储——统一在一个接口之后。 +[事件溯源模型](2026-06-11-event-sourced-sessions.md)将仅追加日志作为唯一真源,并从中派生 LLM(大语言模型)历史。持久化必须忠实于这一设计:直接持久化现有的 `SessionEvent`,不引入需要来回转换的并行「持久化消息」类型。后端也必须可替换——当前用文件存储,以后用数据库存储——统一在一个接口之后。 ## 决策 持久化是一个抽象的**能力 seam**([能力 seam](2026-06-13-capability-seams.md),`dsh-bash` 模板),而非循环或核心逻辑: 1. **接口**(`dsh-session-persistence`,`ctx.sessionPersistence`):一个抽象的 `SessionPersistence` 服务,提供 `create`/`append`/`load`/`list`。其持久化单元就是现有的 `SessionEvent`(`{ type, seq, time, data }`),原样复用,无转换类型。 -2. **实现**(`dsh-session-persistence-jsonl`):每个会话一个仅追加的 JSONL 日志(一行 `SessionHeader`,之后每行一个 `SessionEvent`,逐字节保留,**包括 `assistant/chunk`**)。 +2. **实现**(`dsh-session-persistence-jsonl`):每个会话一个仅追加的逻辑 JSONL 日志(一行 `SessionHeader`,之后每行一个 `SessionEvent`,逐字节保留,**包括 `assistant/chunk`**),默认编码为[带校验和的 Zstandard 帧](2026-07-19-zstandard-jsonl-session-logs.md),也可通过配置使用原始行。 以下关键选择记录于此,因为它们是持久性的、有争议的、且出人意料的: - **规范的持久日志逐字节保留每个 `SessionEvent`,包括 `assistant/chunk`。** `deriveMessages()` 跳过 chunk,而过滤 chunk 的方案(Codex 的 `policy.rs`)很有吸引力,但 `seq = log.length` 以及加载验证 `events[i].seq === i` 要求日志是*连续*的;过滤掉 chunk 会留下空洞,同时破坏契约和恢复功能。基于 chunk 过滤的投影可以作为派生视图在后续实现(带有自己的重新编号),但它不是规范日志。 -- **仅追加;崩溃的轮次被关闭,而非截断。** 已刷写到 `turn/end` 的事件永不被重写,且循环仅在轮次结束时刷写。由于一个被中断的轮次可能包含大量有效工作,`load` 保留其连续、可解析的事件,并为未应答的工具调用追加错误结果、补一个缺失的 `step/end`,以及带 `{ kind: 'interrupted' }` 的 `turn/end`。合成的结果保证恢复后的 provider transcript(文本记录)仍然有效。只有不完整的最后一条记录会被丢弃;在最后一个真实 `turn/end` 处或之前出现解析错误或序号间隙,属于数据损坏,会使该会话不可加载。 +- **仅追加;崩溃的轮次被关闭,而非截断。** 已刷写的事件永不被重写。[语义检查点策略](../bug-fix/2026-07-21-semantic-session-checkpoints.md)会在模型分发前排空请求、在工具分发前排空已记录的顶层调用,并在步骤结束后排空完整的响应/结果批次;循环则排空最终轮次边界。由于一个被中断的轮次可能包含大量有效工作,`load` 保留其连续、可解析的事件,并为未应答的 assistant 调用追加按风险分类的错误结果、补一个缺失的 `step/end`,以及带 `{ kind: 'interrupted' }` 的 `turn/end`。合成的结果保证恢复后的 provider transcript(文本记录)仍然有效。只有不完整的最后一条记录会被丢弃;在最后一个真实 `turn/end` 处或之前出现解析错误或序号间隙,属于数据损坏,会使该会话不可加载。 - **文件后端为规范实现,数据库后端为经过验证的直接替换。** `SessionEvent` 1:1 映射到一行 `(session_id, seq, type, time, data)`:`append` 是 INSERT(在一个断言连续 seq 契约的事务中),`load` 是 SELECT … ORDER BY seq。`dsh-session-persistence-sqlite` 正是如此:一个 `SessionPersistence` 子类,接口无变化(opencode 在 SQLite/WAL 上运行的正是这个形状),且通过与 JSONL 后端相同的 `runPersistenceContract` 测试套件。该契约以相同的语义约束两个后端(惰性物化、加载时关闭中断轮次、连续 seq),一次表达在文件字节上,一次表达在数据库行上。 - **元数据在日志之外。** 格式版本、cwd 和谱系是存储关注点,不是可回放的对话状态,因此它们存放在 `dsh-session` 拥有的 `SessionHeader` 中,并通过新的只读属性 `session.header` 附加到 `Session` 上——永远不进入 `SessionEventMap`,永远不到达 `deriveMessages()`。替代方案(一个可合并扩展的 `session/meta` 事件作为日志第 0 行)被否决:日志内事件会随 seed/fork 的会话免费携带,但元数据不是可回放状态,因此显式的日志外 header seam 是更干净的代价。(header 最初被拆分为不可变的 `SessionHeader` 加可变的 `SessionSummary`,二者的联合类型为 `SessionMeta`;可变 summary 后来因属于死状态而被移除——见 [移除可变会话摘要](../simplification/2026-06-19-drop-mutable-session-summary.md)。) -- **`ctx.agents.create()` 和 `ctx.agents.resume()` 是异步工厂;resume 还跨越持久化边界。** `ctx.agents.resume({ resumeSessionId })` 等待 `ctx.sessionPersistence.load`,用加载的事件重建活跃会话(使 `lastTurnNumber`/`deriveMessages` 得以延续),并在恢复的 id 上启动一个新 agent(不是 `${agentId}-session`)。agent loop(智能体循环)不会硬注入 `sessionPersistence`(那样会让非持久化的演示永远挂起);当它不存在时,`resume` 以明确的错误拒绝。 +- **`ctx.agents.create()` 和 `ctx.agents.resume()` 是异步工厂;resume 还跨越持久化边界。** `ctx.agents.resume({ resumeSessionId })` 等待 `ctx.sessionPersistence.load`,用加载的事件重建活跃会话(使 `lastTurnNumber`/`deriveMessages` 得以延续),并以原样恢复的 id 注册新 agent。agent loop(智能体循环)不会硬注入 `sessionPersistence`(那样会让非持久化的演示永远挂起);当它不存在时,`resume` 以明确的错误拒绝。 ## 曾考虑的替代方案 @@ -33,4 +33,4 @@ Status: implemented ## 后果 -新增两个包(package),以及 `dsh-session` 中的元数据 seam(`session.header`,`create(id?, options?)` 签名)。收益:持久恢复/fork、读取/回放路径、崩溃容忍,以及 ACP `session/load`([ACP 支持](../../implemented/feature/2026-06-14-acp-agent-client-protocol.md))所需的基础——全部基于现有的事件溯源日志,后端在一个接口之后可替换。可复用的 `runPersistenceContract` 测试套件以相同的仅追加、连续 seq、惰性物化与可序列化语义约束每个后端。持久化完整日志还确定了事件保真度:`assistant/chunk` 保持逐字节不变。 +新增两个包(package),以及 `dsh-session` 中的元数据 seam(`session.header`,`create(id?, options?)` 签名)。收益:持久恢复/fork、读取/回放路径、崩溃容忍,以及 ACP `session/load`([ACP 支持](../feature/2026-06-14-acp-agent-client-protocol.md))所需的基础——全部基于现有的事件溯源日志,后端在一个接口之后可替换。可复用的 `runPersistenceContract` 测试套件以相同的仅追加、连续 seq、惰性物化与可序列化语义约束每个后端。持久化完整日志还确定了事件保真度:`assistant/chunk` 保持逐字节不变。 diff --git a/.agents/notes/implemented/architecture/2026-06-15-turn-enclosure-invariant.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-15-turn-enclosure-invariant.i18n.yaml index cf8898285a..46cb7aaa20 100644 --- a/.agents/notes/implemented/architecture/2026-06-15-turn-enclosure-invariant.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-15-turn-enclosure-invariant.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 -2026-06-15-turn-enclosure-invariant.md: 26ae1890e481288b3be17f97db14c332f69be4b8 -2026-06-15-turn-enclosure-invariant.zh.md: 1946f829100e39afed826b8cf3ba82d22058c27d +2026-06-15-turn-enclosure-invariant.md: 6e2abd1716f8efc08c06d5ff8faec38282f2a17f +2026-06-15-turn-enclosure-invariant.zh.md: 0921c2574dc171e887664d8a2ea840a4e81f1531 diff --git a/.agents/notes/implemented/architecture/2026-06-15-turn-enclosure-invariant.zh.md b/.agents/notes/implemented/architecture/2026-06-15-turn-enclosure-invariant.zh.md index 1946f82910..0921c2574d 100644 --- a/.agents/notes/implemented/architecture/2026-06-15-turn-enclosure-invariant.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-15-turn-enclosure-invariant.zh.md @@ -1,4 +1,4 @@ -# RFC: 每个会话事件都封闭在一个轮次内 +# Agent Note: 每个会话事件都封闭在一个轮次内 Status: implemented @@ -20,10 +20,10 @@ Status: implemented **每个会话事件都位于一个轮次内部**:在 `turn/start` 与其匹配的 `turn/end` 之间。具体而言: - agent loop 在 `turn/start` **之后**(轮次内部)追加排队的 `user/message` 事件,而非之前。因此,一旦这些消息被记录,就欠下一个 `turn/end`,既有的 finalizer 保证它被写入。 -- agent **运行中**调用 `agent.inject()` 时,`context/message` 追加到已打开的轮次中(行为不变)。 +- agent **运行中**调用 `agent.inject()` 时,它会加入已打开的轮次。当前步骤执行 assistant 工具调用期间,已接受的上下文按到达顺序等待该批次结算,随后在每个已记录结果之后追加;即使执行中断,也会在轮次关闭前写入。 - agent **空闲时**调用 `agent.inject()`,则将 `context/message` 包裹在一个一次性轮次中:`turn/start{trigger:{kind:'injection'}}` → `context/message` → `turn/end{completed}`。一个新的 `injection` 变体加入可合并扩展的 `TurnTriggerMap`。 - agent loop 每次迭代从日志推导下一个轮次编号(`lastTurnNumber(session) + 1`),而不是维护一个私有计数器,这样空闲注入的一次性轮次不会与下一个真实轮次的编号冲突。 -- `dsh-invariants` 插件在开发环境中**强制执行**该不变式:在没有打开轮次的情况下追加 `user/message` / `context/message` / `steering/message` 会抛出 `InvariantError`。 +- `dsh-session/invariant` companion 将该检查注册到 `ctx.invariants`:选中后,在没有打开轮次的情况下追加 `user/message` / `context/message` / `steering/message` 会抛出归因于 `@deepseek-ai/dsh-session` 的 `InvariantError`。 可序列化性不变式在同一源码边界处强制执行(`Session.append` 对不可 JSON 序列化的数据抛出异常),因此「什么可以进入日志」现在由一个位置统一管控,而非由下游碰巧在监听的某个后端各自发现。 diff --git a/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.i18n.yaml index 62ab944e40..8b36d4bdd1 100644 --- a/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.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 -2026-06-17-filesystem-capability-seam.md: c87c8a44d2e956a9613039378d26f72e8ee97ee7 -2026-06-17-filesystem-capability-seam.zh.md: 4864e77ced5c078fc8c1e970a2ff88c78a37af2a +2026-06-17-filesystem-capability-seam.md: cd8f8572730b1833ed1d7dbe1861d516c2d32925 +2026-06-17-filesystem-capability-seam.zh.md: a7a8d4cbc48c473b1f0669a95c5fbe25eecf94f4 diff --git a/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.zh.md b/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.zh.md index 4864e77ced..a7a8d4cbc4 100644 --- a/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.zh.md @@ -1,4 +1,4 @@ -# RFC: 文件系统能力 seam——ctx.fs、本地后端与面向模型的文件系统工具 +# Agent Note: 文件系统能力 seam——ctx.fs、本地后端与面向模型的文件系统工具 Status: implemented @@ -20,7 +20,7 @@ harness 已有一个具体的 `bash` 能力 seam(`dsh-bash` / `dsh-bash-local` ## 决策 -文件系统访问是一个一等的能力 seam,遵循[能力 seam RFC](../../implemented/architecture/2026-06-13-capability-seams.md): +文件系统访问是一个一等的能力 seam,遵循[能力 seam Agent Note](2026-06-13-capability-seams.md): 1. `@deepseek-ai/dsh-fs`(`packages/fs/fs`)拥有抽象的 `ctx.fs` 服务、文件系统词汇类型,以及 `fs/*` 策略事件词汇。 2. `@deepseek-ai/dsh-fs-local`(`packages/fs/fs-local`)提供第一个实现,以本地文件系统为后端。 @@ -28,7 +28,7 @@ harness 已有一个具体的 `bash` 能力 seam(`dsh-bash` / `dsh-bash-local` 消费方包仅依赖接口包,从不依赖 `dsh-fs-local`。需要不同后端的部署只需为 `ctx.fs` 加载不同的提供方,无需改动工具 schema 或面向模型的提示词引导。 -读后写/编辑与观测状态策略是第四个包 `@deepseek-ai/dsh-fs-policy`(`packages/fs/fs-policy`),通过 `fs/*` 事件门控贡献,而非挂在 `ctx.fs` 上;加载 `dsh-tool-fs` 的部署同时加载 `dsh-fs-policy` 以获得读后写/编辑能力。本 RFC 确立了由三个包构成的 seam;策略从提供方基类拆出的决策由 [split-fs-seam RFC](../simplification/2026-06-26-fsspec-style-fs-seam.md) 做出,其以事件门控插件(而非方法服务)实现的方式由 [event-gate RFC](2026-06-26-file-context-as-event-gate.md) 做出。本文已更新为描述最终落地的四包形态。 +读后写/编辑与观测状态策略是第四个包 `@deepseek-ai/dsh-fs-policy`(`packages/fs/fs-policy`),通过 `fs/*` 事件门控贡献,而非挂在 `ctx.fs` 上;加载 `dsh-tool-fs` 的部署同时加载 `dsh-fs-policy` 以获得读后写/编辑能力。本 Agent Note 确立了由三个包构成的 seam;策略从提供方基类拆出的决策由 [split-fs-seam Agent Note](../simplification/2026-06-26-fsspec-style-fs-seam.md) 做出,其以事件门控插件(而非方法服务)实现的方式由 [event-gate Agent Note](2026-06-26-file-context-as-event-gate.md) 做出。本文已更新为描述最终落地的四包形态。 第一个后端有意仅限本地:`dsh-fs-local` 基于宿主文件系统实现 `ctx.fs`。未来的兄弟后端可在同一接口之后提供沙箱、远程、虚拟或项目作用域的文件系统。 @@ -36,7 +36,7 @@ harness 已有一个具体的 `bash` 能力 seam(`dsh-bash` / `dsh-bash-local` 文件系统权限和沙箱并非此拆分所隐含。本地后端从其配置的基目录解析相对路径,但隔离策略是独立的决策:要么由更严格的 `ctx.fs` 实现强制执行,要么由权限/沙箱插件包装 `tools/execute` 并在调用到达消费方之前否决。 -读后写/编辑与观测状态属于 `dsh-fs-policy`,而非 `ctx.fs`。通过 `fs/*` 事件门控,策略按不透明 actor 记录版本,并提供可选的变更期望;提供方原子性地强制新鲜度。`dsh-tool-fs` 发出事件但不依赖策略。见 [split-seam](../simplification/2026-06-26-fsspec-style-fs-seam.md) 和 [event-gate](2026-06-26-file-context-as-event-gate.md) RFC。 +读后写/编辑与观测状态属于 `dsh-fs-policy`,而非 `ctx.fs`。通过 `fs/*` 事件门控,策略按不透明 actor 记录版本,并提供可选的变更期望;提供方原子性地强制新鲜度。`dsh-tool-fs` 发出事件但不依赖策略。见 [split-seam](../simplification/2026-06-26-fsspec-style-fs-seam.md) 和 [event-gate](2026-06-26-file-context-as-event-gate.md) Agent Note。 ## 包拓扑 @@ -73,7 +73,7 @@ harness 已有一个具体的 `bash` 能力 seam(`dsh-bash` / `dsh-bash-local` - `writeText`/`editText` 接受一个可选的版本期望:省略它表示无条件的裸提供方变更;提供它则在后端的原子临界区内守护变更。 - `dsh-fs-policy` 插件在 `fs/write-intent`/`fs/edit-intent` 上决定该期望,并在 `fs/observed` 上记录观测版本,以它从不透明事件 actor 推导出的 owner 为键(通常是 `exec.agent.session`)。 -授权基于版本新鲜度,而非完整/部分视图的区分:任何读取都会记录目标的版本,后续的写入/编辑只要文件仍处于该版本就被授权——因此对第 100-150 行的窗口化读取可以授权对第 120 行的编辑。观测状态存储是 `dsh-fs-policy` 内部的 `WeakMap>`;`dsh-fs` 不持有任何此类数据,并将 actor 视为不透明。(本 RFC 最初建模了一个带 `full`/`partial` 视图的 `FileState` 缓存放在 `ctx.fs` 上;split-fs-seam 和 event-gate RFC 将其替换为此处描述的基于新鲜度的策略插件。) +授权基于版本新鲜度,而非完整/部分视图的区分:任何读取都会记录目标的版本,后续的写入/编辑只要文件仍处于该版本就被授权——因此对第 100-150 行的窗口化读取可以授权对第 120 行的编辑。观测状态存储是 `dsh-fs-policy` 内部的 `WeakMap>`;`dsh-fs` 不持有任何此类数据,并将 actor 视为不透明。(本 Agent Note 最初建模了一个带 `full`/`partial` 视图的 `FileState` 缓存放在 `ctx.fs` 上;split-fs-seam 和 event-gate Agent Note 将其替换为此处描述的基于新鲜度的策略插件。) 路径解析是显式的,允许异步。本地解析可能只做路径规范化,但沙箱/远程/项目作用域的后端可能需要 I/O 才能将用户提供的路径解析为稳定的目标标识。 @@ -83,7 +83,7 @@ harness 已有一个具体的 `bash` 能力 seam(`dsh-bash` / `dsh-bash-local` - 不透明的 `targetKey`,用于陈旧守护和文件状态查找。本地后端可能使用类似 realpath 的键;远程后端可能使用工作区 URI 或文件 id。消费方禁止解析或假设它是本地绝对路径。 - `displayPath`,用于面向模型/UI 的输出。根据后端不同,它可能是本地绝对路径、工作区相对路径或远程 URI。 -读取和变更结果必须包含不透明的文件 `version`。本地后端可以使用 mtime/size 或类似 hash 的令牌;远程后端可以使用 revision id。`dsh-fs-policy` 插件记录版本用于陈旧检查;消费方可以展示相关元数据但禁止解释版本令牌。 +读取和变更结果必须包含不透明的文件 `version`。本地后端从 bigint stat 元数据(`dev`、`ino`、`size`、`mtimeNs` 和 `ctimeNs`)派生令牌,因此同大小重写和 inode 替换都会可靠地使消费方失效;远程后端可以使用 revision id 或类似 hash 的令牌。`dsh-fs-policy` 插件记录版本用于陈旧检查;消费方可以展示相关元数据但禁止解释版本令牌。 提供方返回已解码的文本:`readText` 返回整个常规文本文件,`streamText` 为大文件流式传输相同的文本语义。两者负责常规文件检查;有界的行/输出处理不是它们的职责——行窗口化、带行号渲染和总行数统计位于执行器(`dsh-tool-fs`)中,执行器通过 `ctx.fs` 读取并渲染面向模型的窗口。提供方负责 UTF-8 解码和二进制/NUL 拒绝;它不知道行窗口或视图。 @@ -137,7 +137,7 @@ harness 已有一个具体的 `bash` 能力 seam(`dsh-bash` / `dsh-bash-local` - **面向模型的工具直接基于 `node:fs`**:工具包将同时承担执行策略、路径解析、原子写入、文本解码和编辑语义,耦合问题部分所列的三个独立变化的关注点,且任何后端替换都会搅动 schema。 - **单一合并包 `dsh-fs-tools`**:seam 之前的形态;以与 bash 相同的接口/实现/消费方拆分理由否决,且合并名称从未成为公开接口。 -- **观测状态放在 `ctx.fs` 上**:本 RFC 最初落地的形态;被 [split-fs-seam RFC](../simplification/2026-06-26-fsspec-style-fs-seam.md) 和 [event-gate RFC](2026-06-26-file-context-as-event-gate.md) 取代:沙箱/远程后端不应继承面向模型的观测策略,因此提供方只保留版本令牌和可选的版本守护变更。 +- **观测状态放在 `ctx.fs` 上**:本 Agent Note 最初落地的形态;被 [split-fs-seam Agent Note](../simplification/2026-06-26-fsspec-style-fs-seam.md) 和 [event-gate Agent Note](2026-06-26-file-context-as-event-gate.md) 取代:沙箱/远程后端不应继承面向模型的观测策略,因此提供方只保留版本令牌和可选的版本守护变更。 ## 后果 @@ -145,11 +145,11 @@ harness 已有一个具体的 `bash` 能力 seam(`dsh-bash` / `dsh-bash-local` **接口可能变得过于本地化。** 如果 `ctx.fs` 返回 `absolutePath` 之类的字段,远程、沙箱或虚拟后端会变得尴尬。契约应暴露显示元数据,而不要求消费方理解宿主路径。 -**接口可能变得过于薄。** 如果 `ctx.fs` 只镜像 `node:fs` 原语,`tool-fs` 将重新实现二进制检测、分页、原子写入和编辑语义,重新制造本 RFC 试图避免的耦合。 +**接口可能变得过于薄。** 如果 `ctx.fs` 只镜像 `node:fs` 原语,`tool-fs` 将重新实现二进制检测、分页、原子写入和编辑语义,重新制造本 Agent Note 试图避免的耦合。 **编辑语义天然易受竞争影响。** 字面编辑是读-改-写操作;守护手段是后端的原子变更临界区加上可选的版本期望,因此并发编辑确定性地收敛——一个赢,另一个得到 `FS_STALE_VERSION`。 -**观测状态不属于 `ctx.fs`。** 记录执行上下文看到了什么是工作流策略,而非原始文件系统 I/O。本 RFC 最初将其放在文件系统 seam 内部;split-fs-seam RFC 随后确立了沙箱/远程后端不应继承面向模型的观测策略,并将其移入 `dsh-fs-policy` 插件。提供方 seam 只保留写入/编辑安全在存储层真正需要的东西——后端铸造的版本令牌和可选的版本守护变更——而策略插件拥有 owner 推导、观测状态和基于 `fs/*` 事件的读后编辑门控。 +**观测状态不属于 `ctx.fs`。** 记录执行上下文看到了什么是工作流策略,而非原始文件系统 I/O。本 Agent Note 最初将其放在文件系统 seam 内部;split-fs-seam Agent Note 随后确立了沙箱/远程后端不应继承面向模型的观测策略,并将其移入 `dsh-fs-policy` 插件。提供方 seam 只保留写入/编辑安全在存储层真正需要的东西——后端铸造的版本令牌和可选的版本守护变更——而策略插件拥有 owner 推导、观测状态和基于 `fs/*` 事件的读后编辑门控。 **`resolve` 然后操作的形态每次调用多一次往返。** 每个工具可能先将路径解析为 `FsTarget`,再以单独的 `ctx.fs` 调用发起读取/写入/编辑。对本地后端来说这可以忽略(解析是内存中的路径规范化),但远程/沙箱后端可能将每步变成独立请求,使单次 `read` 变为两次网络往返。往返开销重要的后端可以在内部缓存或折叠解析,同时保持可观测契约不变。 diff --git a/.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-seams.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-seams.i18n.yaml index 34098ed665..a0ab7d1c83 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-seams.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-seams.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 -2026-06-18-agent-lifecycle-and-ownership-seams.md: c37578020366dd40250c061e34a86d2de6907e19 -2026-06-18-agent-lifecycle-and-ownership-seams.zh.md: 1f3cb609da5508d22046f445e08a75dc5a7901cd +2026-06-18-agent-lifecycle-and-ownership-seams.md: 70ebf1c6de97cb14e27377ec1f9bac18fc0766a6 +2026-06-18-agent-lifecycle-and-ownership-seams.zh.md: 2091182b663dfbd1ac306550de60c1f00d12656f diff --git a/.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-seams.md b/.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-seams.md index 3d84c8074b..70ebf1c6de 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-seams.md +++ b/.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-seams.md @@ -2,6 +2,8 @@ Status: implemented +English | [中文](2026-06-18-agent-lifecycle-and-ownership-seams.zh.md) + ## Problem Several ACP and tool-bash limitations were symptoms of the same missing seam: plugins could create or resume agents through `ctx.agents`, but they could not own and dispose one agent independently, and long-running bash tasks carried no stable owner in the executor itself. ACP aborted and awaited agents on disconnect but could not unregister just that session's agent; `session/cancel` could not cancel queued-but-not-yet-started work; and `tool-bash` kept task ownership in a plugin-local `Map`, so an HMR reload could make an old task look unowned. diff --git a/.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-seams.zh.md b/.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-seams.zh.md index 1f3cb609da..2091182b66 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-seams.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-seams.zh.md @@ -1,4 +1,4 @@ -# RFC: Agent 生命周期与所有权 seam +# Agent Note: Agent 生命周期与所有权 seam Status: implemented @@ -12,36 +12,38 @@ ACP(Agent Client Protocol)与 tool-bash 的若干限制是同一个缺失 se 三个 seam:队列感知的 cancel、`AgentHandle` 释放器,以及 bash 所有者令牌。 -### 1. 队列感知的 `Agent.cancel(reason?)` +### 1. 队列感知的 `Agent.cancel(cause?)` -`cancel()` 是唯一的公开停止原语。它清除已入队和 steering(中途引导)输入、中止正在进行的步骤,并设置一个在每个轮次边界检查的轮次作用域标记。因此,已入队的 prompt 在取消后无法启动,也无法吸收后续输入。`whenIdle()` 等待取消后的静默状态,ACP 的 `session/cancel` 映射到此方法。对空闲状态的 cancel 不设置标记。 +`Agent` 接口新增 `cancel()` 动词——唯一的公开停止原语。(它最初与范围更窄、仅作用于步骤的 `abort()` 一同交付;后者后来因无人使用而移除,使 `cancel()` 成为唯一公开的停止工作方式。)它清空 inbox 的 queued + steering FIFO,在存在活跃轮次时中止它,并保留一个不带 cause 的 pre-run 标记,使在取得所有权前被取消的 prompt 永不运行,而后来的 prompt 仍保持独立。有效调用会在清空或中止前发出 `agent/cancel-requested`,携带类型化的 `user | parent` cause;空闲取消不发出任何事件,也不会使下一条 prompt 搁浅。`whenIdle()` 会到达取消后的静默状态,ACP 的 `session/cancel` 映射到 `user`。[显式轮次取消决策](2026-07-16-explicit-turn-cancellation.md)拥有当前的 cause、signal 生命周期与协作式结算契约。 ### 2. `AgentHandle` 异步释放器 -`ctx.agents.create`/`resume` 和 `AgentFactory` 返回 `AgentHandle = { agent, dispose() }`。释放是消费方的能力;仅持有 `Agent` 的观察者无法将其拆除。调用方 fiber 和 factory 提供方也拥有该实例,所有路径共享一个 memoize 的拆除过程:停止循环、等待静默与刷写完成、分离 agent 和会话,然后解除其 scope。ID 在注册表条目分离后变为可复用。由配置创建的 agent 归 loop fiber 所有;ACP 存储并 dispose 每个会话的 handle。 +`ctx.agents.create`/`resume`(以及 `AgentFactory` 接口)返回 `AgentHandle = { agent: Agent; dispose(): Promise }`。释放器是一种**消费方能力**——仅持有裸 `Agent` 的注册表观察者无法将其拆除。调用方 fiber 和已注册的 factory 提供方是结构上的共同所有者:调用方卸载强制结构化所有权,而提供方卸载必须停止旧实例,因为其实例作用域的依赖 surface 通过该提供方解析。三条路径都会进入同一个 memoize 的拆除过程:停止循环、等待其退出与空闲刷写完成(真正的静默,而非仅把状态翻转为 `disposed`)、分离 agent、分离其会话,然后解除其 scope。每个公开 ID 在其精确注册表条目分离时变得可复用;不存在独立的保留释放阶段。由配置创建的 agent 已归 `AgentLoop` fiber 所有(handle 被丢弃)。ACP 在其 `SessionRecord` 中保存每个会话的释放器,并在断连/拆除时运行它,因此单纯的客户端断连不会留下已注册 agent 或 session-store 条目——即使 `session/load` 与拆除竞争(刚恢复的 handle 会在 closed-guard 抛出前释放)。 -拆除顺序对持久性至关重要。会话生命周期与循环共享一个复合 Cordis effect,因此 LIFO 释放会先停止循环并等待 `agent.done`,然后再分离会话。若使用兄弟 effect,则会并发释放,可能在关闭刷写之前就移除 append 钩子。释放通知被隔离,不会中断拆除链。 +**拆除顺序对持久性至关重要**,实现将会话生命周期折叠进 agent 的单个复合 Cordis effect(`SessionStore.prepare`/`enter`/`announce`,取代兄弟 effect 拆分)。fiber 卸载会并发释放兄弟 effect(`Promise.all`),这会让 session store 的 append 发布钩子移除与循环关闭时的 `session/flush` 竞争,从而丢失关闭的 `turn/end`;在一个 effect 内,释放器作为有序的 LIFO 链运行(停止循环 + `await agent.done` 在会话分离之前),因此无论 handle 的 `dispose()` 还是 fiber 卸载,都会捕获循环的最终刷写。被隔离的 `agent/disposed` 和 `session/disposed` 通知无法拒绝该链或跳过后续拆除。 ### 3. Bash seam 中的所有者令牌 -后台任务的所有权属于执行器。`BashExecSpec.owner` 携带一个可选的不透明令牌,`ownerOf(id)` 读取它,`dsh-tool-bash` 在启动时盖上调用方的会话令牌。`bash_output` 和 `bash_kill` 拒绝不匹配的调用方;完成通知通过注册表按会话令牌定位存活的 agent。将所有权保存在任务上,使得这道隔离在工具插件重载后依然有效。完成监听器仍然是 effect 作用域的,因此在重载间隙到达的通知仍可能被丢弃。 +后台任务所有权从 `tool-bash` 插件本地的 `Map` 移入执行器。`BashExecRequest` 新增可选的 `owner?: string`;解析后的 `BashExecSpec` 将其作为必需但可空的 `owner: string | undefined` 携带(被遗忘的 owner 是可见的 `undefined`,而非静默缺失的属性)。执行器把 token 存在任务上,并通过新的 `BashExecutor.ownerOf(id): string | undefined` seam 暴露它(不放在公开的 `BashTask` 上——只有一条读取路径,没有冗余 API)。`tool-bash` 完全删除其 `Map`:它在 `start` 时将 `exec.agent?.id`(共享的注册表/会话 id)盖章为 owner,`bash_output`/`bash_kill` 则以 `!== undefined` 语义把 `ctx.bash.ownerOf(id)` 与调用方 token 比较(空字符串 token 仍是真实 owner)。完成通知通过扫描 `ctx.get('agents')?.list()` 查找 `agent.id === ownerToken` 的存活 agent(经 `ctx.get` 读取——`onTaskDone` 运行在 bash fiber 这一外部 fiber 上,直接使用 `ctx.agents` proxy 会抛异常)。由于所有权现在存活在执行器的任务上(随 `dsh-bash` fiber dispose),它能跨越 `tool-bash` HMR 重载,关闭旧的 `XXX(tool-bash-owner-hmr)` 缺口。(`onTaskDone` 监听器仍受 `tool-bash` 的 `apply` effect 约束,因此落在重载间隙的完成仍会丢失一条通知——既有的重载间隙丢失——但所有权隔离本身已经不受 HMR 影响。) ## 验证 -- ACP 断连或会话关闭后,不留下任何已注册的 agent 或 session-store 条目,包括 `session/load` 与拆除竞争的情况。 -- 在已入队的 prompt 启动前取消,能阻止该 prompt 运行或吸收下一条 prompt。 -- 重载 `dsh-tool-bash` 不会让另一个会话读取或终止已有的后台任务,因为所有权保留在执行器上。 -- 由配置创建的 agent 仍归 loop fiber 所有,因此非 ACP 演示无需显式管理 handle。 +以下不变式已经成立,并由测试固定: + +- ACP 断连/会话关闭后,不留下该会话的任何已注册 agent 或 session-store 条目,即使 `session/load` 与拆除竞争。 +- 已入队的 prompt 启动前执行 `session/cancel`,能阻止该 prompt 运行;后来接受的 prompt 仍是独立的已入队轮次。 +- `tool-bash` HMR 重载不会使另一个会话能够读取或终止已有的后台任务(所有权保留在执行器上)。 +- 既有的非 ACP 演示无需显式管理 handle 仍能工作;由配置创建的 agent 仍归 `AgentLoop` 插件 fiber 所有。 ## 会话所有者令牌在存活 agent 中唯一 -bash 所有者令牌依赖 `session.header.id` 在存活 agent 中的唯一性。并发的同 ID 操作可以私下准备,但 `SessionStore.enter()` 拒绝重复发布,失败的事务回滚。`tool-bash` 拥有比较策略;bash seam 存储一个不透明的 `owner` 字符串,不对其做解释。 +bash 所有者 token 比较依赖共享的 `Agent.id`/`SessionId` 在存活 agent 中唯一。并发的同 ID 操作可以都私下准备,但发布会依次进入 session 和 agent;`SessionStore.enter()` 拒绝重复的存活 session id,每个失败事务都回滚自己的私有状态。因此程序化调用方无法发布两个共享同一会话 token 的存活 agent。访问*策略*(token 比较)留在消费方 `tool-bash`;bash seam 只存储不透明的 `owner` 字符串且从不解释它——这是正确的接口/实现/消费方拆分。 ## 曾考虑的替代方案 - **公开的 `BashTask.owner` 字段**而非 `BashExecutor.ownerOf(id)` seam:否决。一条读取路径即可,无需冗余 API。 - **为 agent 的会话生命周期使用兄弟 Cordis effect**:否决。fiber 卸载时并发释放兄弟 effect(`Promise.all`),store 拥有的 append 发布钩子的移除与循环的关闭 `session/flush` 产生竞争;单一复合 effect 的有序 LIFO 链才能在两条释放路径上都捕获关闭的 `turn/end`。 -- **在 `cancel()` 之外另设一个仅中止步骤的 `abort()`**:最初发布过,后因无人使用而移除;`cancel()` 是唯一的公开停止原语(见[公开停止接口 RFC](../simplification/2026-06-20-public-agent-stop-surface.md))。 +- **在 `cancel()` 之外另设一个仅中止步骤的 `abort()`**:最初发布过,后因无人使用而移除;`cancel()` 是唯一的公开停止原语(见[公开停止接口 Agent Note](../simplification/2026-06-20-public-agent-stop-surface.md))。 ## 后果 diff --git a/.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml index c319a372fe..578187d1aa 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-18-session-surface.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 -2026-06-18-session-surface.md: bcad236eaa2bead3c140e7a12772729710d43f83 -2026-06-18-session-surface.zh.md: 2ec60501c183d1bdab22ceb67b7f6aa0040ae82b +2026-06-18-session-surface.md: 80034881d0112076759a68737b5931c8ff659d15 +2026-06-18-session-surface.zh.md: 49303c87aa87569bcc61f215642c9b7f0c408dbf diff --git a/.agents/notes/implemented/architecture/2026-06-18-session-surface.md b/.agents/notes/implemented/architecture/2026-06-18-session-surface.md index f1297b1f05..80034881d0 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-session-surface.md +++ b/.agents/notes/implemented/architecture/2026-06-18-session-surface.md @@ -2,6 +2,8 @@ Status: implemented +English | [中文](2026-06-18-session-surface.zh.md) + ## Problem The event log is authoritative, but history manipulation had no durable shared mechanism. Plugins such as compaction would otherwise rewrite derived requests through order-sensitive listeners, leave no provenance, and require repeated changes to `deriveMessages()`. diff --git a/.agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md b/.agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md index 2ec60501c1..49303c87aa 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md @@ -1,4 +1,4 @@ -# RFC: 会话 surface——基于事件日志的链表,用于 LLM 消息派生 +# Agent Note: 会话 surface:事件日志上的有序投影 Status: implemented @@ -10,13 +10,13 @@ Status: implemented ## 决策 -新增一个 **surface**:一条派生的、缓存的链表,由「surface 节点」(事件中产出 LLM(大语言模型)消息的子集)组成,通过事件日志中的 `surfaceOp` 标记维护。 +新增一个 **surface**:事件 seq 的派生、缓存有序投影(即产出 LLM(大语言模型)消息的事件子集),通过事件日志中的 `surfaceOp` 标记维护。 ### `SessionEvent` 新增两个顶层字段 每个 `SessionEvent` 获得两个可选字段(结构性元数据,与 `seq`/`time` 同级): -- **`sourceEventSeqs?: number[]`**:作为溯源来源的事件 seq 编号(例如构成 `assistant/message` 的各 `assistant/chunk` 的 seq,或被压缩标记遮蔽的 surface 节点)。溯源是核心设计原则;没有它,replace-range 操作在回放时无法被验证。 +- **`sourceEventSeqs?: number[]`**:作为溯源来源的事件 seq 编号(例如构成 `assistant/message` 的各 `assistant/chunk` 的 seq,或被压缩标记遮蔽的 surface 节点)。出现的 `[]` 只在 `assistant/message` 上有效,表示已知为空的提供方流;在该事件上省略字段表示旧数据或未记录的溯源。其他 surface 事件一旦出现此字段,就必须是非空列表。溯源是核心设计原则;没有它,replace-range 操作在回放时无法被验证。 - **`surfaceOp?: SurfaceOp`**:该事件如何进入 surface。非 surface 事件不携带此字段。 ### SurfaceOp:两种操作 @@ -27,13 +27,13 @@ export type SurfaceOp = | { op: 'replace'; start: number; end: number } // shadow [start, end] inclusive ``` -1. **Append**:在尾部追加一个新节点。`user/message`、`assistant/message`、`tool/result`、`context/message`、`steering/message` 使用此操作。agent loop(智能体循环)在所有此类追加上传入 `surfaceOp: 'append'`,并在适用时附带 `sourceEventSeqs`(例如 `assistant/message` 记录其 `assistant/chunk` 来源;`tool/result` 记录其 `tool/call` 来源)。 +1. **Append**:在尾部追加新事件的 seq。`user/message`、`assistant/message`、`tool/result`、`context/message`、`steering/message` 使用此操作。agent loop(智能体循环)在所有此类追加上传入 `surfaceOp: 'append'`,并在适用时记录 `sourceEventSeqs`:每个成功的 `assistant/message` 都记录完整的 `assistant/chunk` 来源集合(包括 `[]`),而 `tool/result` 记录其 `tool/call` 来源。 -2. **Replace**:移除从 `start` 到 `end`(两端包含)的节点,并在其位置插入一个新节点。`start` 和 `end` 都必须是当前 surface 上有效的 surface 节点 seq;`start === end` 表示替换单个节点。该节点的 `sourceEventSeqs` 必须包含所有被遮蔽的 surface 节点。被遮蔽的事件仍留在日志中,但不再出现在 surface 上。 +2. **Replace**:移除从 `start` 到 `end`(两端包含)的条目,并在其位置插入新事件的 seq。`start` 和 `end` 都必须存在于当前 surface;`start === end` 表示替换单个条目。该事件的 `sourceEventSeqs` 必须包含所有被遮蔽的 surface seq。被遮蔽的事件仍留在日志中,但不再出现在 surface 上。 ### SurfaceManager:基于增量,而非全量重建 -`SurfaceManager` 类(`Session` 私有)维护缓存的链表。它跟踪 `_lastProcessedSeq`,仅处理**增量**(自上次访问以来的新事件),而非重新扫描整个日志。由于日志是仅追加的,先前的事件不会改变;种子日志只是在首次访问时折叠的初始增量。 +一个 `Session` 拥有一个 `SurfaceManager`,后者维护事件 seq 的有序 `number[]`。管理器会在提交前校验每个种子或追加候选项而不应用它,然后只处理上次同步之后已经提交的事件,而不重新扫描整个日志。`Session.surface` 通过只读的 `SessionSurface` 契约暴露同一个管理器,因此接纳、派生历史、压缩与工作区上下文共享同一份增量状态。Replace 按数组位置找到两端都包含的端点,并把替换 seq splice 到该范围;不会用第二个管理器、链接对象或 seq 到节点的 map 来重复表达顺序。 无新事件时增量处理为 O(1),有新事件到达时为 O(新事件数)。 @@ -49,23 +49,25 @@ export type SurfaceOp = ### 不变式 -开发模式下的不变式插件验证:`sourceEventSeqs` 引用(非空、无重复、引用更早的事件、引用已知 seq)以及 `surfaceOp`(replace 的 `start ≤ end`、两个端点都在被跟踪的 surface 上、范围在 surface 位置上不反转、`sourceEventSeqs` 包含该范围遮蔽的每个节点)。 +`Session` 在始终启用的 seed/append 边界校验 `sourceEventSeqs` 与 `surfaceOp`:只有 `assistant/message` 可以使用空的溯源列表;引用必须唯一、更早且已知;替换端点必须存在于 surface 顺序中;溯源必须覆盖每个被遮蔽的节点。这些是单记录接纳与存储投影规则,不是可选的不变式服务贡献。 每个 surface 可达事件都必须携带 `surfaceOp`,否则它将从派生历史中消失。类型化的 `append` 重载对字面事件类型强制执行此规则;`append` 和种子构造函数中的运行时检查覆盖宽化联合类型和加载的日志。按照预发布格式策略,无效的种子被拒绝而非升级。 ## 曾考虑的替代方案 - **逐插件的 `agent/request` 包装**(surface 之前的历史操纵模式):监听器排序脆弱、无法持久记录改动内容,且每种新操纵都迫使核心 `deriveMessages()` 再次修改。 -- **半开区间 `[start, endExclusive)` 的 replace 范围**:否决。surface 是双向链表,端点自然以节点 seq 命名,单节点替换(`start === end`)在闭区间语义下读起来更自然。 +- **半开区间 `[start, endExclusive)` 的 replace 范围**:否决。端点由 surface 事件 seq 命名,单条目替换(`start === end`)在闭区间语义下读起来更自然。 +- **链接节点对象加 seq map**:否决。生产代码不读取前驱链接,唯一的后继用途就是数组中的下一个位置,而替换本来就需要线性 `indexOf` 查找。单个 seq 数组在保留相同渐进复杂度的同时,只留下一个需要校验的表示。 - **脏标记后全量重建**替代增量处理:在会话生命周期内为 O(N²),每次单事件追加都要重新扫描所有先前事件。 ## 后果 -- **`packages/core/session`**:新增 `surface.ts`(`SurfaceManager`)、新类型(`SurfaceOp`、`SurfaceIntent`)、`SessionEvent` 新字段、修改 `append()`(第三个必选参数 `SurfaceIntent`)、重构 `deriveMessages()`(以 surface 遍历作为唯一派生路径)、surface 感知的 `repair.ts`。种子构造函数拒绝缺少 `surfaceOp` 标记的 surface 可达种子事件(见「不变式」一节)。 +- **`packages/core/session`**:`surface.ts`(`SurfaceManager`)维护一个用于候选接纳和实时投影的有序 seq 数组;`SessionSurface` 是其只读公共视图。`SurfaceOp`/`SurfaceIntent` 与顶层会话事件字段记录条目如何加入它。`append()` 要求 surface 事件携带 `SurfaceIntent`,`deriveMessages()` 以遍历 surface 作为唯一派生路径,`repair.ts` 则发出 surface 感知的闭合事件。种子构造函数拒绝缺少 `surfaceOp` 标记的 surface 可达种子事件(见「不变式」一节)。 - **`packages/core/agent-loop`**:所有 surface 可达的追加操作传入 surface 选项。收集 chunk seq 用于 `assistant/message` 溯源;捕获 `tool/call` seq 用于 `tool/result` 溯源。 - **`packages/session-persistence/session-persistence-sqlite`**:`events` 表新增两个可空 TEXT 列(`source_event_seqs`、`surface_op`);`SCHEMA_VERSION` 递增(bump-and-reject,无迁移)。 -- **`packages/support/invariants`**:surface 相关验证规则。 - **`packages/session-persistence/session-persistence-jsonl`**:无需改动。 - **`packages/session-persistence/session-persistence`**:抽象接口不变。 -Surface 是未来历史操纵的基础。压缩或 tool-result-prune 插件追加一个既有的消息产出事件类型(例如一条携带摘要的 `user/message`),附带 `surfaceOp: { op: 'replace', start, end }` 和覆盖被遮蔽节点的 `sourceEventSeqs`——新节点在 surface 上取代该范围的位置,而插件自身的 trace 事件(如 `compaction/start`、`compaction/end`)不进入 surface。回放以确定性方式保留该决策。 +Surface 是未来历史操纵的基础。压缩或 tool-result-prune 插件追加一个既有的消息产出事件类型(例如一条携带摘要的 `user/message`),附带 `surfaceOp: { op: 'replace', start, end }` 和覆盖被遮蔽条目的 `sourceEventSeqs`——新事件在 surface 上取代该范围的位置,而插件自身的 trace 事件(如 `compaction/start`、`compaction/end`)不进入 surface。回放以确定性方式保留该决策。 + +一次 `tool/result` 替换只能改写当前的一个 `tool/result`,并且必须保留除 `content` 以外的每个数据字段。Session 接纳会与位置范围和溯源校验一起强制这条规则,不依赖可选的诊断插件。 diff --git a/.agents/notes/implemented/architecture/2026-06-18-shared-persistence-write-coordinator.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-18-shared-persistence-write-coordinator.i18n.yaml index e960781d12..8d572c486f 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-shared-persistence-write-coordinator.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-18-shared-persistence-write-coordinator.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 -2026-06-18-shared-persistence-write-coordinator.md: 7648c6f8e43fb8c78f881838aa4d476aba209dca -2026-06-18-shared-persistence-write-coordinator.zh.md: 19f583c2f3d78fcdac1378cfe00e49c0be943eb1 +2026-06-18-shared-persistence-write-coordinator.md: 12800b118bd2fd1189319d0edf37cace3d916a9c +2026-06-18-shared-persistence-write-coordinator.zh.md: 23d6c66989880369c4e26c38145559c198ea1033 diff --git a/.agents/notes/implemented/architecture/2026-06-18-shared-persistence-write-coordinator.zh.md b/.agents/notes/implemented/architecture/2026-06-18-shared-persistence-write-coordinator.zh.md index 19f583c2f3..23d6c66989 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-shared-persistence-write-coordinator.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-18-shared-persistence-write-coordinator.zh.md @@ -1,4 +1,4 @@ -# RFC: 共享持久化写入协调器 +# Agent Note: 共享持久化写入协调器 Status: implemented @@ -12,7 +12,9 @@ Status: implemented 将一个后端无关的 `PersistenceCoordinator` 提取到 `dsh-session-persistence` 中。协调器统一拥有编排逻辑;每个第一方后端组合一个协调器实例(`new PersistenceCoordinator(ctx, this)`),实现一个小型 `PersistenceBackend` 钩子接口,并将其四个公开服务方法(`create`/`append`/`load`/`list`)委托给协调器。 -组合,而非继承。协调器是后端持有的具体类,不是后端继承的基类。本 RFC 的风险——「协调器不得让非常规后端与继承层级作斗争」——由此规避:后端只暴露钩子;它无法触及协调器的私有编排状态,且公开的 `SessionPersistence` 服务形状不变,因此第三方后端仍然可以完全不使用协调器、直接实现抽象服务。 +组合,而非继承。协调器是后端持有的具体类,不是后端继承的基类。本 Agent Note 的风险——「协调器不得让非常规后端与继承层级作斗争」——由此规避:后端只暴露钩子;它无法触及协调器的私有编排状态,且公开的 `SessionPersistence` 服务形状不变,因此第三方后端仍然可以完全不使用协调器、直接实现抽象服务。 + +协调器通过每个存活会话的 `session/disposed` 通知将其退役:等待该 Session 对象自身的初始化,串行执行最后一次排空,随后移除其拥有的状态、缓冲区和初始化条目。排空失败时保留缓冲区,供后端 teardown(拆除)重试。每个 id 的已结算链尾仅在其仍是当前链尾时才移除自身,因此旧操作完成后不会抹除同一 id 的新操作。后端 teardown 会先注销写入路径监听器,再等待所有已准入的退役、剩余缓冲区和链,最后关闭后端。 ### 钩子接口(`PersistenceBackend`) @@ -28,11 +30,11 @@ Status: implemented ### 不透明的 torn marker -保持 seam 整洁的唯一设计选择:崩溃修复中「损坏尾部在哪里」的 token 对协调器是不透明的。协调器计算合成 closers(它拥有来自 `dsh-session` 的 `interruptedTurnClosers`),但它只测试 `tornMarker !== undefined` 并将值原样传回 `commitRepair`——从不检视其内容。每个后端选择自己的 marker 类型:JSONL 使用要截断到的字节偏移,SQLite 使用要从其开始删除的 seq(两者恰好都是 `number`)。JSONL 后端将其 `committedBytes < buffer.byteLength` 比较折叠在钩子内部,因此返回的 marker 已经是 `number | undefined`;如果不做这层折叠,协调器就必须了解字节长度。 +保持 seam 整洁的唯一设计选择:崩溃修复中「损坏尾部在哪里」的 token 对协调器是不透明的。协调器计算合成 closers(它拥有来自 `dsh-session` 的 `interruptedTurnClosers`),但它只测试 `tornMarker !== undefined` 并将值原样传回 `commitRepair`——从不检视其内容。每个后端选择自己的 marker 类型:JSONL 携带要截断到的字节偏移,以及从不完整最终帧中解码出的任何完整事件;SQLite 则携带要从其开始删除的 seq。协调器因此既不了解字节长度,也不了解帧恢复状态。 ## 测试 -共享的 `runPersistenceContract`(公开 API 契约)继续为每个后端运行。新增的 `runCoordinatorContract`(`tests/coordinator-contract.ts`)覆盖写入路径编排——接管、HMR、碰撞、dispose 排空、崩溃尾部修复——通过 `CoordinatorFixture`(内存参考实现 + jsonl + sqlite)为每个后端运行一次。各后端自身的测试规格缩减为仅覆盖存储机制(JSONL:路径安全、fsync 回滚、bucket 列举;SQLite:schema 版本、`scanRows`、事务回滚)。每个真实后端有一个经由协调器的 torn-tail→load→`commitRepair` 测试(通过 `corruptTail` fixture(测试前置数据)钩子),确保协调器的 torn-marker 修复分支在 100% per-file 门禁下被覆盖——契约崩溃测试只产生合成 closers 而不产生 torn marker,因此无法触达该分支。 +共享的 `runPersistenceContract`(公开 API 契约)继续为每个后端运行。`runCoordinatorContract`(`tests/coordinator-contract.ts`)覆盖写入路径编排——接管、HMR、碰撞、会话与后端 dispose 排空,以及崩溃尾部修复——通过 `CoordinatorFixture`(内存参考实现 + jsonl + sqlite)为每个后端运行一次。协调器专属测试固定退役 map 清理、同 id 链尾竞态、排空失败后的重试,以及关闭顺序。各后端自身的测试规格只保留存储机制(JSONL:路径安全、fsync 回滚、bucket 列举;SQLite:schema 版本、`scanRows`、事务回滚)。每个真实后端有一个经由协调器的 torn-tail→load→`commitRepair` 测试(通过 `corruptTail` fixture(测试前置数据)钩子),确保协调器的 torn-marker 修复分支在 100% per-file 门禁下被覆盖——契约崩溃测试只产生合成 closers 而不产生 torn marker,因此无法触达该分支。 ## 曾考虑的替代方案 @@ -41,4 +43,4 @@ Status: implemented ## 后果 -协调器增加了一层间接和一个不透明的 torn marker,但将此前每个后端重复的、对正确性要求很高的编排逻辑集中到一处。其钩子面保持窄小:碰撞检查复用 `loadStored`,物化保持在 `appendBatch` 内原子完成,列举绕过协调器。新后端只需实现存储原语,而无需复制事件-缓冲区-flush 生命周期。 +协调器增加了一层间接、一个不透明的 torn marker 和脱离会话生命周期的退役任务,但将此前每个后端重复的、对正确性要求很高的编排逻辑集中到一处。会话 dispose 仍是仅观察事件,因此会话所有者不会等待持久化退役;协调器会收容失败、保留未提交的缓冲区,并以后端 teardown 为静止状态边界。其钩子面保持窄小:碰撞检查复用 `loadStored`,物化保持在 `appendBatch` 内原子完成,列举绕过协调器。新后端只需实现存储原语,而无需复制事件-缓冲区-flush 生命周期。 diff --git a/.agents/notes/implemented/architecture/2026-06-20-branded-ids.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-20-branded-ids.i18n.yaml index 7d33e94f0a..29c4f4724d 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-branded-ids.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-20-branded-ids.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 -2026-06-20-branded-ids.md: aab47a1413451edb707ae797e5b99cda6e34efad -2026-06-20-branded-ids.zh.md: 1ccfda3282069510bcdbfbddf4546305fd0923a5 +2026-06-20-branded-ids.md: 93bab1d47c793cc4dd1f1d19fa3af22d1721be29 +2026-06-20-branded-ids.zh.md: f45f39cc3a49f7abf116e77f1ace2db74fc3de67 diff --git a/.agents/notes/implemented/architecture/2026-06-20-branded-ids.md b/.agents/notes/implemented/architecture/2026-06-20-branded-ids.md index e7a3110fce..93bab1d47c 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-branded-ids.md +++ b/.agents/notes/implemented/architecture/2026-06-20-branded-ids.md @@ -2,6 +2,8 @@ Status: implemented +English | [中文](2026-06-20-branded-ids.zh.md) + ## Problem The harness brands `CallId` (`packages/llm/llm/src/brand.ts`) and the shared agent/session `SessionId` (`packages/core/session/src/types.ts`) using the `Branded = string & { readonly [BRAND]: B }` machinery (owned by the type-only `@deepseek-ai/dsh-brand` package at `packages/util/brand/` — see its [README](../../../../packages/util/brand/README.md)) and a zero-cost cast factory per type. `dsh-brand` also states the governing policy: *"Branding is for ids that cross package boundaries and could plausibly be confused; not every string needs a brand."* That policy is right; the problem is that it is only half-applied. Two gaps let a structurally-identical-but-semantically-wrong string slip through the type checker today. diff --git a/.agents/notes/implemented/architecture/2026-06-20-branded-ids.zh.md b/.agents/notes/implemented/architecture/2026-06-20-branded-ids.zh.md index 1ccfda3282..f45f39cc3a 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-branded-ids.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-20-branded-ids.zh.md @@ -1,4 +1,4 @@ -# RFC: 在所有应有之处使用 branded ID +# Agent Note: 在所有应有之处使用 branded ID Status: implemented @@ -6,23 +6,23 @@ Status: implemented ## 问题 -harness 已经为三个标识符做了 brand 处理:`CallId`(`packages/llm/llm/src/brand.ts`)、`SessionId`(`packages/core/session/src/types.ts`)和 `AgentId`(`packages/core/agent/src/types.ts`),使用 `Branded = string & { readonly [BRAND]: B }` 机制(由纯类型包(package) `@deepseek-ai/dsh-brand` 拥有,位于 `packages/util/brand/`,见其 [README](../../../../packages/util/brand/README.md)),并为每个类型提供零开销的 cast 工厂。`dsh-brand` 还声明了治理策略:*"Branding 用于跨包边界且可能被混淆的 id;不是每个 string 都需要 brand。"* 这条策略是正确的;问题在于它只落实了一半。两处缺口使得结构相同但语义错误的 string 今天仍能通过类型检查器。 +harness 使用 `Branded = string & { readonly [BRAND]: B }` 机制,为 `CallId`(`packages/llm/llm/src/brand.ts`)和共享的 agent/session `SessionId`(`packages/core/session/src/types.ts`)做 brand 处理;该机制由纯类型包(package) `@deepseek-ai/dsh-brand` 拥有,位于 `packages/util/brand/`,见其 [README](../../../../packages/util/brand/README.md),并为每个类型提供零开销的 cast 工厂。`dsh-brand` 还声明了治理策略:*「Branding 用于跨包边界且可能被混淆的 id;不是每个 string 都需要 brand。」* 这条策略是正确的;问题在于它只落实了一半。两处缺口使得结构相同但语义错误的 string 今天仍能通过类型检查器。 -**缺口 1:bash seam 中未 brand 的 ID。** `BashTask.id` 以及所有执行器/工具边界使用裸 `string`,尽管生成的值与默认 session id 具有相同的 `name-N` 形状。模型还通过 `task_id` 返回该值,因此混淆 task id 和 session id 既类型正确又可达。 +**缺口 1:bash seam 中未 brand 的跨边界 ID。** 后台任务 id 是普通 `string`:`BashTask.id: string`(`packages/bash/bash/src/types.ts`),作为 `string` 贯穿整个执行器 seam(`packages/bash/bash/src/index.ts` 中的 `BashExecutor.get`/`ownerOf`/`readOutput`/`kill(id: string)`),再由面向模型的工具以 `string` 校验并传递(`validateTaskId`、`assertTaskAccess`、`packages/bash/tool-bash/src/index.ts` 中 `task_id` 的 schema 参数)。它由每执行器计数器生成——`packages/bash/bash-local/src/index.ts` 中的 `` `bash-${this.nextTaskId++}` ``——其形状与 `SessionId` 的默认值**完全相同,都是 `name-N`**(`packages/core/session/src/index.ts` 中的 `` `session-${++counter}` ``)。bash task id 和 session id 在调用点轻易就能互换,而编译器毫无反应。这是用户询问的核心案例,并且它是面向模型的 id(模型会把 `task_id` 传回 `bash_output`/`bash_kill`),所以该混淆可由不受信任的输入触达。 -bash **owner token** 是相关的子情形:`BashExecRequest.owner?: string` 和 `BashExecSpec.owner: string | undefined`(`packages/bash/bash/src/types.ts`)被文档描述为刻意*不透明*的隔离键,但在所有实际调用方中,该值就是所属 agent(智能体)的 `session.header.id`(`callerToken = (exec) => exec.agent?.session.header.id`,位于 `packages/bash/tool-bash/src/index.ts`),即一个穿着 `string` 外衣的 `SessionId`。它被用于访问控制比较(`owner !== callerToken(exec)`),因此一个不匹配但类型正确的 string 在此处就是一个跨会话隔离 bug,而当前类型系统无法捕获。这正是 [unify-the-agent-id-and-the-session-id](../../proposed/simplification/2026-06-20-unify-agent-and-session-id.md) 提案所称的以 `session.header.id` 作为 owner 的别名缺口("bash owner-token alias hole")。 +bash **owner token** 是相关的子情形:`BashExecRequest.owner?: string` 和 `BashExecSpec.owner: string | undefined`(`packages/bash/bash/src/types.ts`)被文档描述为刻意*不透明*的隔离键,但在所有实际调用方中,该值就是所属 agent(智能体)共享的 `Agent.id`/`SessionId`(`callerToken = (exec) => exec.agent?.id`,位于 `packages/bash/tool-bash/src/index.ts`),只是披着另一个 seam 本地名称。它被用于访问控制比较(`owner !== callerToken(exec)`),因此一个不匹配但类型正确的 string 在此处就是跨会话隔离 bug,而当前类型系统无法捕获。这正是[统一 agent/session 标识决策](../simplification/2026-06-20-unify-agent-and-session-id.md)覆盖的共享 id 别名。 -**缺口 2:既有 brand 的侵蚀。** `CallId`、`SessionId` 和 `AgentId` 在注册表 map、公开查找参数、ACP 会话跟踪和持久化协调器中退化为裸 string。在查找边界丢弃 brand 会使其主要保护失效。 +**缺口 2:*已经 brand* 的 ID 在 seam 处被侵蚀。** 就连 `CallId` 和 `SessionId` 也恰好在最容易混淆的地方退化为裸 `string`:注册表/store 键类型和公开方法参数。代表性位置包括 session store、agent 注册表(二者都以共享的 `SessionId` 为键)、`ToolPresenter` 的 call-id map、ACP 的 session-id 记录和 loading set,以及持久化协调器。在集合键处丢弃 brand,会让既有 brand 在查找时毫无价值;它们的价值只实现了一部分。 ## 决策 -纯类型变更。Brand 是零开销 cast;运行时行为、序列化、比较和协议格式(wire format)均不变。工作分三部分,全部遵循既有的"不是每个 string 都需要"策略。 +纯类型变更。Brand 是零开销 cast;运行时行为、序列化、比较和协议格式(wire format)均不变。工作分三部分,全部遵循既有的「不是每个 string 都需要」策略。 -- **为 bash task id 加 brand。** 在 `packages/bash/bash/src/types.ts`(*拥有*该 id 的包)中添加 `BashTaskId = Branded<'BashTaskId'>` 及其同名工厂,从 `@deepseek-ai/dsh-brand` 导入 `Branded`,方式与 `SessionId`/`AgentId` 完全一致。brand 原语位于无依赖的 `dsh-brand` 工具包中,正是为了让 `dsh-bash` 仅依赖它就能为自己的 id 加 brand,而无需引入 `dsh-llm`(或 `dsh-session`)来获取 `Branded`。将其贯穿 `BashTask.id`、`BashExecutor` seam 方法(`get`/`ownerOf`/`readOutput`/`kill`)、`dsh-bash-local` 中的生成点(在创建时对计数器输出做一次 brand),以及 `dsh-tool-bash` 的校验/访问面(`validateTaskId` 返回 `BashTaskId`;`task_id` 在模型 string 到达的工具边界处被 brand)。 +- **为 bash task id 加 brand。** 在 `packages/bash/bash/src/types.ts`(*拥有*该 id 的包)中添加 `BashTaskId = Branded<'BashTaskId'>` 及其同名工厂,从 `@deepseek-ai/dsh-brand` 导入 `Branded`,方式与 `SessionId` 完全一致。brand 原语位于无依赖的 `dsh-brand` 工具包中,正是为了让 `dsh-bash` 仅依赖它就能为自己的 id 加 brand,而无需引入 `dsh-llm`(或 `dsh-session`)来获取 `Branded`。将其贯穿 `BashTask.id`、`BashExecutor` seam 方法(`get`/`ownerOf`/`readOutput`/`kill`)、`dsh-bash-local` 中的生成点(在创建时对计数器输出做一次 brand),以及 `dsh-tool-bash` 的校验/访问面(`validateTaskId` 返回 `BashTaskId`;`task_id` 在模型 string 到达的工具边界处被 brand)。 -- **铸造独立的 `OwnerToken` brand。** 在 `packages/bash/bash/src/types.ts` 中添加 `OwnerToken = Branded<'OwnerToken'>`;将 `BashExecRequest.owner` / `BashExecSpec.owner` / `BashExecutor.ownerOf` 的类型标注为 `OwnerToken | undefined`。`dsh-tool-bash` 消费方在边界处将 agent 的 `session.header.id`(一个 `SessionId`)cast 为 `OwnerToken`——这是两套词汇唯一交汇的地方。bash seam 从不导入 `dsh-session`。(理由见下一节。) +- **铸造独立的 `OwnerToken` brand。** 在 `packages/bash/bash/src/types.ts` 中添加 `OwnerToken = Branded<'OwnerToken'>`;将 `BashExecRequest.owner` / `BashExecSpec.owner` / `BashExecutor.ownerOf` 的类型标注为 `OwnerToken | undefined`。`dsh-tool-bash` 消费方在边界处将 agent 共享的 `id`(`SessionId`)cast 为 `OwnerToken`——这是两套词汇唯一交汇的地方。bash seam 从不导入 `dsh-session`。(理由见下一节。) -- **阻止 brand 侵蚀。** 将既有 brand 传播到缺口 2 列出的 `Map` 键类型和公开方法参数中:`Map`、`get(id: SessionId)`、`Map`、`Map`、ACP 的 `SessionRecord.sessionId: SessionId` 接口、协调器的 `Map`。这是 diff 中机械量最大的部分,也是让*既有* brand 在查找处真正发挥作用(而不仅仅标注在结构体字段上)的关键。 +- **阻止 brand 侵蚀。** 将既有 brand 传播到缺口 2 列出的 `Map` 键类型和公开方法参数中:`Map`、`Map`、`get(id: SessionId)`、`Map`、ACP 的 `SessionId` surface、协调器的 `Map`。这是 diff 中机械量最大的部分,也是让*既有* brand 在查找处真正发挥作用(而不仅仅标注在结构体字段上)的关键。 示意形状(工厂模式与已有的三个 brand 完全一致): @@ -46,24 +46,24 @@ export function OwnerToken(id: string): OwnerToken { ### 为什么不把 `owner` 类型标注为 `SessionId`? -执行器将 ownership 视为不透明的,不应依赖 session 模型。独立的 `OwnerToken` 保留了这一边界,同时防止裸 string 或 task id 被当作 owner 传入。`dsh-tool-bash` 拥有访问策略,由它执行来自 `SessionId` 的唯一转换。 +显而易见的捷径是直接把 `owner` 类型标注为 `SessionId`——它确实*总是*一个 session id。我们否决这个方案。bash 执行器 seam 是能力 seam(接口 `dsh-bash`、实现 `dsh-bash-local`、消费方 `dsh-tool-bash`),其 owner token 被*明确记录为刻意不透明*:执行器「从不解释它(seam 中没有访问策略——那是消费方的职责)」(`packages/bash/bash/src/types.ts`)。把 seam 字段类型标注为 `SessionId`,会把 `dsh-session` 的词汇引入一个不应知道 owner token *含义*的包——这会让通用执行后端耦合 session 模型,并违背不透明 token 的设计。取代 `dsh-bash-local` 的沙箱或远程执行器不应继承 session 依赖。独立的 `OwnerToken` brand 使 seam 保持解耦:`dsh-bash` 只知道「owner 是某种带 brand 的不透明 token」,而已经决定访问策略的 `dsh-tool-bash` 消费方,是把其 `SessionId` cast 为 `OwnerToken` 的唯一边界。该 brand 仍带来安全收益(不能把 `BashTaskId` 或裸 string 传到 owner 位置),且不引入耦合。 ## 不在范围内 / 可能的扩展 -遵循"不是每个 string 都需要 brand"的策略,刻意保持窄范围。以下每项都是合理的未来 brand 候选,附带推迟理由而非承诺: +遵循「不是每个 string 都需要 brand」的策略,刻意保持窄范围。以下每项都是合理的未来 brand 候选,附带推迟理由而非承诺: -- **`ModelId`**(`GenerateOptions.model`,`LlmService` 适配器注册表的键):一个真正的跨包查找键(config → agent → llm → adapter);合理的下一个 brand,仅为控制本 RFC 的影响范围而暂不纳入。 +- **`ModelId`**(`GenerateOptions.model`,`LlmService` 适配器注册表的键):一个真正的跨包查找键(config → agent → llm → adapter);合理的下一个 brand,仅为控制本 Agent Note 的影响范围而暂不纳入。 - **`ToolName`**(`ToolRegistry` 的键):由作者定义、人类可读,且很少与其他 id 混淆;最弱的候选,可能不值得加 brand。 - **`ErrorCode`**(`HarnessError.code`):一个封闭词汇(`ABORTED`、`NO_ADAPTER`……),不是逐实例的 id;如果要做,string 字面量联合类型比 brand 更合适。 - **数值序号**:轮次号、步骤号和事件 `seq` 是 `number` 而非 `string`,`Branded` 不适用;可以用并行的 `number & { readonly [BRAND]: B }` 变体来 brand 它们,但它们是位置序号、很少跨边界传递,收益较低。 -- **带校验的构造**:brand 工厂是纯 cast,无运行时检查,且每个边界(ACP `sessionId`、提供方签发的 `call.id`、`dsh-llm-deepseek` 中的空字符串回退)今天都信任裸 string。一个在边界处对格式错误的输入抛异常的 `SessionId.parse()` / `isValid()` 配套工具确实是缺口,但它是*运行时行为*变更,有自己的设计问题(什么算"格式错误"?失败时怎么办?),应在独立 RFC 中处理,不应捆绑进这次纯类型变更。 +- **带校验的构造**:brand 工厂是纯 cast,无运行时检查,且每个边界(ACP `sessionId`、提供方签发的 `call.id`、`dsh-llm-deepseek` 中的空字符串回退)今天都信任裸 string。一个在边界处对格式错误的输入抛异常的 `SessionId.parse()` / `isValid()` 配套工具确实是缺口,但它是*运行时行为*变更,有自己的设计问题(什么算「格式错误」?失败时怎么办?),应在独立 Agent Note 中处理,不应捆绑进这次纯类型变更。 ## 验证 -`BashTaskId` 和 `OwnerToken` 定义在 `dsh-bash` 中,贯穿执行器、本地实现和面向模型的工具,且未添加 `dsh-session` 依赖。集合、公开参数和导出签名对 `CallId`、`SessionId`、`AgentId` 或 `BashTaskId` 使用相应的 brand 而非裸 `string`;来自提供方、ACP 和模型的原始输入通过 brand 工厂进入,而非散落的 cast。 +已落地的不变式:`BashTaskId` 和 `OwnerToken` 定义在 `dsh-bash` 中,并端到端贯穿执行器 seam、`dsh-bash-local` 生成点与 `dsh-tool-bash` 面向模型的 surface,且 `dsh-bash` 未添加对 `dsh-session` 的依赖;没有任何以范围内 brand id(`CallId`/`SessionId`/`BashTaskId`)为键的集合使用裸 `string`;公开方法参数和导出签名保留 brand;每个原始 string 进入的边界(提供方 call id、ACP session id、模型提供的 `task_id`)都通过 cast 工厂构造 brand,而不是散落的 `as` cast。 ## 后果 -- **两个接口面的机械性改动。** 传播 brand 涉及 bash seam(接口 + 实现 + 消费方)以及 ACP session-id 接口和持久化协调器。改动面广但严重度低:遗漏的位置是编译错误而非静默 bug。变更可观察地为纯类型变更——无快照或 e2e 行为差异。它与 [unify-the-agent-id-and-the-session-id](../../proposed/simplification/2026-06-20-unify-agent-and-session-id.md) 提案相邻(两者都触及 session-id / owner-token 边界);如果该提案落地,`OwnerToken` 出于上述解耦理由仍与统一后的 id 保持独立。 -- **Brand 不做校验。** Brand 是混淆防护,不是正确性证明:一个*错误的* session id 只要仍是合法的 string,就和以前一样能通过类型检查器。本 RFC 不关闭这个缺口(见"不在范围内")——它只阻止这类*类别*错误:传入错误*种类*的 id。 -- **"在哪里停下"仍是判断题。** 为 `BashTaskId` 加 brand 但不为 `ToolName` 加,为 `OwnerToken` 加但不为 `ModelId` 加,是对哪些 string"可能被混淆"的品味判断。合理的评审者可能想要更多或更少;`brand.ts` 中的策略是裁决依据,本 RFC 倾向于面向模型或用于访问控制的 id。 +- **两个接口面的机械性改动。** 传播 brand 涉及 bash seam(接口 + 实现 + 消费方)以及 ACP session-id 接口和持久化协调器。改动面广但严重度低:遗漏的位置是编译错误而非静默 bug。变更可观察地为纯类型变更——无快照或 e2e 行为差异。它与[统一 agent/session 标识决策](../simplification/2026-06-20-unify-agent-and-session-id.md)相邻,因为二者都触及 session-id / owner-token 边界;`OwnerToken` 出于上述解耦理由仍与统一后的 id 保持独立。 +- **Brand 不做校验。** Brand 是混淆防护,不是正确性证明:一个*错误的* session id 只要仍是合法的 string,就和以前一样能通过类型检查器。本 Agent Note 不关闭这个缺口(见「不在范围内」)——它只阻止这类*类别*错误:传入错误*种类*的 id。 +- **「在哪里停下」仍是判断题。** 为 `BashTaskId` 加 brand 但不为 `ToolName` 加,为 `OwnerToken` 加但不为 `ModelId` 加,是对哪些 string「可能被混淆」的品味判断。合理的评审者可能想要更多或更少;`brand.ts` 中的策略是裁决依据,本 Agent Note 倾向于面向模型或用于访问控制的 id。 diff --git a/.agents/notes/implemented/architecture/2026-06-20-extract-example-app-packages.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-20-extract-example-app-packages.i18n.yaml index 602102a219..419c158b01 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-extract-example-app-packages.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-20-extract-example-app-packages.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 -2026-06-20-extract-example-app-packages.md: b0dd1fe32b2389578d3f3bd3672435923eb1a5d1 -2026-06-20-extract-example-app-packages.zh.md: 9f47c98c9c54357a304490e3d4075fad6cea855a +2026-06-20-extract-example-app-packages.md: b757a0099382a648a4efda4639e275ae6e6a02d1 +2026-06-20-extract-example-app-packages.zh.md: 5814704f0426707ef255a67a7aed9b79cae4aa24 diff --git a/.agents/notes/implemented/architecture/2026-06-20-extract-example-app-packages.zh.md b/.agents/notes/implemented/architecture/2026-06-20-extract-example-app-packages.zh.md index 9f47c98c9c..5814704f04 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-extract-example-app-packages.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-20-extract-example-app-packages.zh.md @@ -1,4 +1,4 @@ -# RFC: 将示例应用提取为独立包 +# Agent Note: 将示例应用提取为独立包 Status: implemented @@ -8,29 +8,28 @@ Status: implemented 示例目录本应是*精简的*——只包含演示的可变接线,而非演示的基础设施。在此次变更之前,它是臃肿的。每个示例都携带一份手写的 `start.ts` 启动引导、一段基础设施前导(`timer`,以及 stdio 演示所需的 `logger` + `hmr`(热模块替换))、三个共享 YAML 片段的嵌套引用(`base.yml` / `base-core.yml` / `acp-agent/acp-tail.yml`),还有各示例自身的 `agent-loop`/persistence/system-prompt 配置。真正的应用——每个 agent(智能体)都需要的服务主干——散落在叶子配置和那些 include 中。 -叶子配置还拥有一个耦合的前门。ACP(Agent Client Protocol)要求 stdout 纯净,并通过 `session/new` 创建 agent;stdio 则需要一个控制台 logger 和一个预创建的 `main`。防止错误组合的唯一屏障是文档中的文字警告,而三个 `start.ts` 文件重复着 Loader 引导和生命周期代码。 +叶子配置还拥有耦合的前门。ACP(Agent Client Protocol)要求 stdout 纯净,并通过 `session/new` 创建 agent;终端应用和 Headless 应用则预创建 `main`,但进程 I/O 契约不同。防止错误组合的唯一屏障是文档中的文字警告,而三个 `start.ts` 文件重复着 Loader 引导和生命周期代码。 ## 决策 每个示例现在**主要是对一个应用包(package)的调用**,沿着既有的[接口 / 实现 / 消费方 seam](2026-06-13-capability-seams.md) 拆分接线:**应用包拥有组合**,叶子 `cordis.yml` 只拥有**可替换的选择**(哪个 LLM(大语言模型)适配器、哪个 bash 执行器、模型、提示词、持久化根目录)。 - **`@deepseek-ai/dsh-agent-spine-demo`**([packages/examples/agent-spine-demo](../../../../packages/examples/agent-spine-demo))组合了不含 provider、不含执行器、不含 UI 的主干,并转发 agent loop(智能体循环)的 agent 列表配置。它对具体 loop 的依赖是有意为之,因为该包组合的是主干而非扩展主干;替换 loop 意味着提供另一个 bundle。 -- **`@deepseek-ai/dsh-stdio-demo`**([packages/examples/stdio-demo](../../../../packages/examples/stdio-demo))和 **`@deepseek-ai/dsh-acp-demo`**([packages/examples/acp-demo](../../../../packages/examples/acp-demo))各自内置了前门。Stdio 包含 `ui-stdio`、控制台 logger 和 `main`;ACP 包含 bridge 和 JSONL 持久化,但不含 stdout logger 或预创建的 agent。叶子可以添加插件,但安全的组合现在是默认产物。 -- **`start.ts` 已移除。** 每个应用包暴露一个 `bin`(`dsh-stdio-demo` / `dsh-acp-demo`);`demo:*` 脚本调用它(例如 `dsh-stdio-demo ./cordis.yml`)。Loader 引导尾部、`.env` 加载和快速失败守卫位于共享的 [`@deepseek-ai/dsh-app-boot`](../../../../packages/ui/app-boot) 包(在逐文件覆盖率门禁下有单元测试——见[共享应用 bin 的启动胶水](../simplification/2026-07-04-share-app-bin-boot-glue.md));每个 bin 是一个精简的自执行组合,基于这些辅助函数加上其应用特有的生命周期逻辑(ACP bin:快照模式选择与 stdin-dispose)。`bin.ts` 文件本身仍被排除在覆盖率之外(自执行 CLI(命令行界面)入口,与旧的 `start.ts` 性质相同),由 keyless 的 Loader 路径测试驱动。 -- **每个叶子 `cordis.yml` 精简为**后端 + 配置:LLM 适配器(带 apiKey/models 的 `llm-deepseek`,或 `llm-replay`)、bash 执行器(`bash-local`)、stdio 演示的 `hmr`(见下方修正),以及一个承载应用配置的 app 条目(模型、系统提示词、持久化根目录——以应用包自身的 `Config` 形式暴露,由它将各值路由到应用接线的目标位置:stdio 路由到预创建的 agent,acp 路由到 bridge 插件)。 -- **echo-agent 折叠到 `dsh-stdio-demo` 上**,将 LLM 后端替换为本地的 `mock-llm`,并在叶子层添加本地的 `echo-tool`(加上 `bash-local`,由主干的 `tool-bash` 注入)——这是「替换后端、保留应用」的干净示范。`mock-llm.ts` / `echo-tool.ts` 作为示例本地的教学插件保留。 +- **`@deepseek-ai/dsh-tui-demo`**、**`@deepseek-ai/dsh-cli-demo`** 和 **`@deepseek-ai/dsh-acp-demo`** 各自内置其进程角色。TUI 包含全屏 UI 和预创建的 `main`;Headless 包含 one-shot driver 和预创建的 `main`;ACP 包含 bridge 且不预创建 agent。三者都包含 JSONL 持久化,并省略 stdout logger。 +- **`start.ts` 已移除。** 每个应用包都暴露一个 bin;`demo:*` 脚本调用它。Loader 引导、`.env` 加载和快速失败守卫位于共享的 [`@deepseek-ai/dsh-app-boot`](../../../../packages/ui/app-boot) 包(在逐文件覆盖率门禁下有单元测试——见[共享应用 bin 的启动胶水](../simplification/2026-07-04-share-app-bin-boot-glue.md));精简的自执行入口由 keyless 的 Loader 路径测试驱动。 +- **每个叶子 `cordis.yml` 精简为**后端、可选产品工具,以及一个承载应用配置的 app 条目。TUI 和 Headless 把模型/会话选择路由到预创建的 agent;ACP 把初始提供方/模型路由到 bridge。 - **`base.yml`、`base-core.yml` 和 `acp-agent/acp-tail.yml` 已退役**——它们共享的主干现在位于 `dsh-agent-spine-demo` 中。 `bash-local` 和 LLM 适配器仍然是**叶子选择**:bundle 提供 `tool-bash`(消费方 schema),叶子选择执行器实现,因此沙箱执行器或回放适配器无需触碰应用即可替换。 ### 实现修正:`hmr` 保留为叶子条目 -提案最初将 `hmr` 列入 stdio 应用内置的前门集群。对照代码验证后发现,将 `hmr` 内置到 `dsh-stdio-demo` 包中会在两个方面与 Cordis 冲突,因此改为作为**叶子 `cordis.yml` 条目**交付: +提案最初将 `hmr` 列入交互式应用内置的前门集群。对照代码验证后发现,将 `hmr` 内置到应用包中会在两个方面与 Cordis 冲突,因此改为作为**叶子 `cordis.yml` 条目**交付: 1. `@cordisjs/plugin-hmr` 是一个仅限 Loader、仅限子进程的开发插件——其构造函数在没有 `node --expose-internals` 和活跃的 `loader` 服务时会抛出异常,因此只能在真实的 `demo:*`/bin 子进程中运行,不能在进程内的单元/覆盖率测试层运行。 2. 进程内测试层(vitest)甚至无法*导入* vendor 的 `hmr` 模块(其 class-decorator `@Inject` 形式在 Vite 的 transform 下会失败),因此一个 `apply` 静态导入了它的包永远无法满足其主函数的逐文件 100% 覆盖率门禁。 -关键在于,`hmr` **不是**像控制台 logger 那样的 stdout 纯净隐患:ACP 配置中误加 `hmr` 不会破坏 JSON-RPC 帧,因此将它留在叶子层不会损失耦合论证所关注的安全性。**logger**(真正的耦合点)保持内置:stdio 应用包含它,ACP 应用省略它。 +关键在于,`hmr` 不是 stdout 纯净隐患:ACP 配置中误加该条目不会破坏 JSON-RPC 帧。所有已交付应用都省略 stdout 控制台 logger;stdout 只归应用或协议 driver 所有。 ## 曾考虑的替代方案 @@ -41,13 +40,13 @@ Status: implemented ## 验证 - 示例目录只包含配置、README 和测试:`start.ts`、基础设施前导和共享 YAML include 已移除。 -- `demo:echo`、`demo:repl` 和 `demo:acp` 调用应用包的 bin。 -- 每个新包都有 README 和逐文件 100% 覆盖率;每个应用包还有一个 keyless 的真实 Loader 路径 bin 冒烟测试,用于捕获[事后分析 0001](../../../postmortem/0001-acp-default-export-drops-inject.md) 中描述的导出形状故障。 +- `demo:tui`、`demo:headless` 和 `demo:acp` 调用应用包的 bin。 +- 每个新包都有 README 和逐文件 100% 覆盖率;每个应用包还有一个 keyless 的真实 Loader 路径 bin 冒烟测试,用于捕获[事后分析 0001](../../../../docs/postmortem/0001-acp-default-export-drops-inject.md) 中描述的导出形状故障。 - ACP 回放 transcript(文本记录)保持不变,因为插件集合和加载顺序未改变。 ## 后果 -- **裸插件树的教学性。** echo-agent 内联的 `cordis.yml` 曾一次展示所有插件;主干现在隐藏在 bundle 之后,查看完整树意味着打开 `dsh-agent-spine-demo`。应用包的 README 承担了这份教学职责。 +- **裸插件树的教学性。** 主干现在隐藏在 bundle 之后,查看完整树意味着打开 `dsh-agent-spine-demo`。应用包的 README 承担了这份教学职责。 - **多了一层间接。**「这个演示加载了什么?」从扫描单个 YAML 变成了阅读一个包。 ## 相关 @@ -55,3 +54,4 @@ Status: implemented - 取代 [Make the shared example base providerless](../../rejected/architecture/2026-06-20-providerless-example-base.md):一旦主干移入 `dsh-agent-spine-demo` 且 `base*.yml` 文件被删除,将 `base.yml` 重命名为无 provider 核心便不再有意义。 - 基于 [capability-seams](2026-06-13-capability-seams.md) 的接口/实现/消费方拆分——后端和展示层保持为叶子选择;主干是共享 bundle。 - 与 [Reorganize packages into a modular hierarchy](2026-06-20-package-hierarchy.md) 互补:新的 app/core 包按该层级结构归入既有分组(`core` 放可复用的主干 bundle,`ui` 放应用特有的前门)。 +- 后续的[冗余 agent 移除](../simplification/2026-07-20-remove-stdio-and-echo-agents.md)拥有最终的 TUI/Headless 拆分,并移除行式与仅 mock 的叶子。 diff --git a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml index b98f0fa227..af587f2c25 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.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 -2026-06-20-generic-long-running-tool-runtime.md: 9b83a4443d6d654cda75c72fba3369a16be078e2 -2026-06-20-generic-long-running-tool-runtime.zh.md: c1684be4fd5c1c3c9d913e063f4ba66bb6064459 +2026-06-20-generic-long-running-tool-runtime.md: c28236d194ae839f88d900f0fc52707b2c9e53e2 +2026-06-20-generic-long-running-tool-runtime.zh.md: 911c4288fff6e4235b9c88efeaaa882910e559b0 diff --git a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md index 4db0d78910..c28236d194 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md +++ b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md @@ -2,6 +2,8 @@ Status: implemented +English | [中文](2026-06-20-generic-long-running-tool-runtime.zh.md) + ## Problem Background bash originally combined two responsibilities: the bash executor ran processes and also managed task ids, ownership, incremental reads, cancellation, completion listeners, and model-facing control tools. Adding background subagents required the same lifecycle and interaction contract. Implementing that contract independently for every long-running capability would duplicate isolation, cleanup, notification, and prompt behavior while teaching the model a different collect-and-stop protocol for each producer. diff --git a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md index c1684be4fd..911c4288ff 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md @@ -1,43 +1,130 @@ -# RFC: 提取通用的长时间运行工具运行时 +# Agent Note: 后台任务运行时(`ctx.tasks`)与通用任务控制工具 -Status: proposed +Status: implemented [English](2026-06-20-generic-long-running-tool-runtime.md) | 中文 ## 问题 -bash 能力 seam 同时支持前台命令和长时间运行的后台任务。后台支持体量不小:抽象执行器暴露 `start`、`get`、`ownerOf`、`list`、`readOutput`、`kill` 和 `onTaskDone`;本地执行器负责跟踪任务、增量读取、owner token、进程清理和完成监听;模型侧看到三个工具(`bash`、`bash_output`、`bash_kill`);工具插件将完成通知注入回所属 agent(智能体)的会话。本地执行器用 owner token 隔离任务访问,因为可预测的全局 task id 会带来跨会话的读取/终止风险。 +后台 bash 原本兼有两项职责:bash 执行器既运行进程,又管理 task id、所有权、增量读取、取消、完成监听器和面向模型的控制工具。新增后台 subagent 需要相同的生命周期与交互契约。如果每种长时间运行能力都独立实现该契约,就会重复隔离、清理、通知和提示词行为,还会让模型为每种生产方学习不同的收集与停止协议。 -[工具实操手册](../../../cookbook/adding-a-tool.md)已经指出了真正的设计异味:后台 bash 本质上是寄居在单个工具内部的通用长时间运行工具基础设施。如果未来的工具也需要后台执行、轮询、终止、所有权和完成通知,这些语义不应藏在 `dsh-bash` 里。 +任务注册表、控制工具与完成通知共同构成一项 harness 功能。bash 和 subagent 只提供执行专属的钩子,不拥有通用任务行为。 -## 提案 +## 决策 -将长时间运行任务的语义从 bash 上移到一个与工具无关的运行时中。bash 仍然能运行后台命令,但不再拥有 task id、ownership token、轮询、取消、完成通知以及模型侧「读取/终止此任务」命令等通用概念。 +`tasks/` 包组拥有后台任务语义: -该运行时应拥有: +- `@deepseek-ai/dsh-tasks` 将运行中的工作注册为 `ctx.tasks`,并拥有 task id、授权、快照、读取、取消、等待、完成监听器与清理。 +- `@deepseek-ai/dsh-tool-tasks` 暴露 `task_output`、`task_list` 和 `task_kill`,注入完成通知,并提供后台任务的系统提示词指导。 -- 稳定的 task id 和 owner token,按调用方的会话/agent 做键。 -- 注册一个长时间运行任务,附带增量输出的生产者和一个完成 promise。 -- 通用的 read/cancel/list 操作,对所有工具使用相同的跨会话授权规则。 -- 向所属会话注入完成通知。 -- 针对 pending/running/completed 任务状态的展示钩子,bash 只提供命令特有的标签和输出格式化。 +长时间运行工具是生产方。`dsh-tool-bash` 将 `BashProcess` 适配为增量输出与进程取消;`dsh-tool-subagent` 将子运行适配为最终输出与子运行释放。执行 seam 保持独立,不依赖会话或任务注册表。 -`dsh-bash` 保留 bash 特有的执行契约:将请求解析为命令规格、运行前台命令,或启动进程并将其流/进程句柄交给通用运行时。`dsh-tool-bash` 保留模型侧的命令工具,但后续操作变为通用的长时间运行工具操作,或者 bash 向其注册的共享工具,而不是专属的 `bash_output`/`bash_kill` 管道。 +`TaskService` 是一个具体的进程内服务。TODO(task-service-backend):当第二个后端明确所需生命周期后,将其公共契约与实现分离;systemd 驱动的运行时是一种可能方案,但本 PR(Pull Request)不臆测其持久性、重连、所有权或观察语义。 -## 当前 seam 消费情况 +## 运行时契约 -当前消费方划分清晰:`dsh-tool-bash` 使用完整的前台/后台 seam,而钩子桥接层只使用前台的 `resolve` 和 `run`(带受信的 `stdin` 和 `env`)。`get` 和 `list` 仅在测试中使用;`BashTask.done` 仅在实现内部用于 dispose(资源释放),生产环境的完成通知使用 `onTaskDone`。提取出的运行时应暴露一个公开的完成机制,保留钩子所需的简单前台路径,并决定后台的 `timeoutMs` 是否属于 `start`。如果它拥有进程 spawn 的职责,还应集中处理目前重复的凭证清洗逻辑。 +字面类型见[任务数据结构目录](../../../../docs/core-data-structures/tasks.md)。生产方调用 `ctx.tasks.start()`,传入 kind、label、可选的所属 `Agent` 与一个 `run()` 函数。运行时会在调用 `run()` 前完成所有可能失败的预检工作,并且只调用一次。`run()` 返回钩子后,注册过程不会再执行可能失败的步骤而直接提交;生产方无法启动没有可收集 task id 的工作。 -## 验收标准 +生产方钩子定义三项职责: -- bash 特有的包(package)不再定义通用的任务注册表、owner-token 授权、轮询、取消或完成通知机制。 -- 一个共享的长时间运行任务服务或工具层拥有这些语义,并被文档化为未来任何具备后台能力的工具的接入路径。 -- bash 后台行为仍可通过共享层使用,测试证明跨会话隔离依然成立。 -- ACP 和快照 fixture(测试前置数据)通过共享任务词汇渲染后台 bash,而非通过 bash 专属的生命周期语义。 -- [工具实操手册](../../../cookbook/adding-a-tool.md)将长时间运行工具指向共享运行时,而不是让每个工具自行发明任务协议。 +- `cancel(reason?)` 同步请求终止,具备幂等性,并且必须使 `done` 完成。 +- `done` 从不拒绝,并且仅在生产方释放任务资源后完成。 +- 可选的 `readOutput()` 返回下一个消费式输出增量。省略该钩子即声明这是最终输出任务,其终止结果来自 `TaskOutcome.output`。 -## 风险 +状态包括 `running`、`stopping`、`completed`、`killed` 和 `failed`。退出码或停止原因等生产方专属信息放在 `detail` 中,注册表不解释这些信息。任务 kind 构成可合并扩展的字符串联合;task id 带品牌,并按 `-N` 生成,每个 kind 各有一个计数器。 -bash 包失去了对一个已经可用的后台任务实现的本地所有权,实现 PR(Pull Request)可能暂时搅动模型侧的工具名称或 transcript(文本记录)展示。如果最终结果是留下一份后台任务契约,而不是让每个未来的长时间运行工具克隆 bash 的私有协议,这种搅动是值得的。 +运行时为 `done` 附加一个 continuation,记录第一个终止结果、解决等待方,并逐个调用完成监听器,同时隔离每个监听器的错误。首次结果优先的结算在资源销毁期间至关重要:如果 `cancel` 抛出,运行时会强制将记录标为失败,并警告工作可能遗留,而不是永远等待一个可能永不完成的 promise。后续生产方结果不能覆盖该诊断,也不能重复通知。`cancel` 返回后如果最终未使 `done` 完成,仍会阻塞资源销毁,因为运行时无法区分这种情况与缓慢但有效的停止。 - +任务注册不是生产方工具 fiber 的 effect。因此,重新加载工具或控制接口插件不会终止由 agent(智能体)和后端拥有的工作。任务服务自身释放时会取消所有实时任务,并等待遵守契约的生产方。 + +## 授权与所有者生命周期 + +task id 在运行时全局可见且可预测,因此注册表会授权每次访问。`get`、`read`、`wait` 和 `kill` 接受调用方 `Agent`;`list` 仅返回该调用方可见的任务。有所有者的任务仅允许对应的确切会话访问。无所有者任务向非 agent 调用方开放,并随任务服务一起终止。 + +快照存储所有者的品牌化 `SessionId` 以供授权,生命周期操作则保留确切的实时 `Agent` 实例。这两种身份用途不同:会话相等性授予访问权,精确对象身份决定清理和完成通知的接收方。复用 agent 或会话 id,不能将旧作用域的清理或通知重定向到替代实例。 + +某个所有者的第一个任务会向 `owner.ctx` 附加一个异步 effect。agent 作用域释放时会取消该所有者的实时任务、等待其终止记录,并移除其快照。该 effect 可跨生产方重载存续,并加入 agent 现有的停稳边界。任务服务保留 effect disposer,使服务重载可以在全局资源销毁后,从仍然存活的 agent 作用域中分离回调。 + +对于遵守契约的生产方,`AgentHandle.dispose()` 只在所属后台工作停止后解决。需要比 agent 存活更久的工作必须以无所有者方式启动;要跨运行时重启存续,则需另行设计持久任务。 + +## 服务接口 + +`TaskService` 提供: + +- `start(spec)`:经过预检的原子注册。 +- `get(id, caller?)` 和 `list(caller?)`:非消费式快照。 +- `read(id, caller?)`:消费式流增量或幂等的最终结果。 +- `kill(id, caller?, reason?)`:取消。 +- `wait(id, timeoutMs, caller?, signal?)`:有界的终止等待。 +- `onTaskDone(listener)`:effect 作用域内的观察,具有精确所有者投递和监听器隔离。 +- `attachSurface(name)`:控制接口可用性防线。 + +`wait` 在任务完成时返回终止快照,在等待超时时返回实时快照。中止一次等待只取消该次等待。如果结算已经将终止投递分配给该等待方,终止快照仍然优先。等待方在中止时同步注销,因此同一 tick 内的结算无法代表一个什么也未收到的读取方压制完成通知。 + +如果生产方加载时没有任何控制接口,调用方就能启动无法收集或停止的工作。因此,`dsh-tool-tasks` 在其整个生命周期内调用 `attachSurface()`;没有附加接口时,`start()` 会在生产方开始执行前失败。该检查发生在启动时而非插件加载时,因为兄弟插件可能并发激活。自定义的非模型接口可以自行附加,无需让注册表了解工具名称。 + +## 面向模型的控制接口 + +`dsh-tool-tasks` 注册三个与 kind 无关的工具,并使用通用 ACP 卡片: + +- `task_output(task_id, wait?, timeout_ms?)` 读取输出,并始终追加 `[status: ...]`。流式任务只返回上次读取以来的输出;最终输出任务在结算后返回结果。除非指定 `wait: true`,否则读取不会阻塞;等待超时由插件配置提供默认值并限定上限。等待超时会报告仍在运行的状态,不会停止任务。 +- `task_list()` 将调用方可见的任务返回为 ` []