Archive 21 implemented triplets whose shipped decisions are complete and whose bodies no longer guide future work (one-off UI chrome, generator applications whose scripts are self-explanatory, superseded implementation detail, process history owned by current contracts/skills). Delete 4 rejected triplets whose premises are obsolete: the DeepReadonly proposal (dev-invariants note now carries the alternative inline), the collapse tool-owned presentation proposal (superseded by the shipped render-intent union), retire-mid-turn-steering (steering is now load-bearing across plan-mode/apiproxy/TUI), and single-session-ACP (automation-only ACP resolved the question; multi-session isolation is pinned by tests). Repair every inbound link: retarget intentional historical citations to archived paths, replace decision-current citations with the surviving authority, and fix the stale example-execute-over-tsx pointer in pnpm-workspace.yaml. Re-record pairing sidecars and seal the archive manifest (append-only; existing seals unchanged).
6.6 KiB
Agent Note: Reorganize packages into a modular hierarchy
Status: implemented Archived: 2026-07-27
English | 中文
The redundant-agent removal deletes the original support/ui-stdio surface instead of relocating it, and the automation-only ACP decision places ACP under packages/acp/acp instead of the human-UI group. The uniform depth-two hierarchy remains the decision owned here.
Problem
packages/ was flat: 18 packages all sat at packages/<name>/, so a package's location said nothing about whether it was core product API, a swappable capability seam, a provider adapter, a product integration, or example/test support. The package README carried a FIXME(package-hierarchy) and scripts/publint-all.ts a TODO(package-inventory) flagging exactly this. Core packages, provider integrations, capability seams, example UI support, and snapshot-only replay support all looked equally foundational.
This was not just cosmetic. Because every top-level package looked like part of the same public surface, future removal was harder, and publish/lint/doc scripts had to encode intent through comments or hand-maintained static lists rather than reading it off the layout.
Decision
Packages are grouped by modular role at a uniform packages/<group>/<pkg>/ depth. Group directories are pure containers (no package.json); every package keeps its @deepseek-ai/dsh-<pkg> name — this is repo structure and maintenance policy, not package renaming.
packages/
core/ (product API spine)
session/
system-prompt/
tools/
agent/
agent-loop/
llm/ (product — capability family)
llm/
llm-deepseek/
llm-pi-ai/
bash/ (product — capability family)
bash/
bash-local/
tool-bash/
session-persistence/ (product — capability family)
session-persistence/
session-persistence-jsonl/
session-persistence-sqlite/
acp/ (product automation integration)
acp/
ui/ (human interaction and presentation)
support/ (dev/test/example infrastructure)
invariants/
ui-stdio/
llm-replay/
Placement decisions
- Same-name nesting for capability families. A family's interface package sits at
packages/<group>/<group>/(llm/llm,bash/bash,session-persistence/session-persistence), with implementations and consumers as flat siblings. There is no extraadapters//impls/sub-tier — every package is exactly depth 2, which keeps the workspace glob a cleanpackages/*/*and lets one@deepseek-ai/dsh-*tsconfig wildcard resolve every package (unique dir names make first-on-disk-wins unambiguous). sessionstays incore/; persistence is its own family. The session log is core product API. Its storage backends form a parallel capability family (session-persistence/) mirroringllm/andbash/, rather than nesting undercore/session/.agent-loopis incore/. It is the one concrete implementation of theagentseam, but it ships as the harness's default product loop, so it lives with the core spine. Plugins still depend on theagentvocabulary, never onagent-loop, so the loop stays swappable.- Product automation and human UI are separate groups.
acpis a product transport underacp/, while commands, approvals, interaction, and presentation adapters live underui/. Dev-only invariants and replay infrastructure remain undersupport/.
Deduplicating the package lists
The package list had been enumerated in five places. The uniform depth-2 layout lets most of them be derived instead:
tsconfig.base.jsonmaps every package through a single@deepseek-ai/dsh-*pathswildcard listing one candidate per group, in place of per-package entries. The aggregate configs (tsconfig.host.json,tsconfig.client.json) reuse that source map and carry the explicit project references that keep package/vendor typecheck boundaries intact. (One subtlety this introduced: a path candidate contains/*/, which a naive regex comment-stripper mistakes for a block comment —scripts/doc-typecheck.tsreads the JSONC config through TypeScript's parser rather than stripping comments by hand for exactly this reason.)scripts/publint-all.tsderives its list by reading the hierarchy (packages/<group>/<pkg>), resolving theTODO(package-inventory).- The aggregates' project
referencesstay explicit lists — TypeScript project references have no wildcard form. Generating these from a manifest is left to a follow-up (see discover package inventories).
Guardrails added
Two doc-sync/hygiene gates keep the structure and its references honest, so the manual checks this restructure required do not have to be repeated by hand:
scripts/verify-package-paths.tsflags apackages/<path>reference (in Markdown or a.tscomment/string) that does not resolve and names a real package in a segment — i.e. a stale path to a moved package. A path naming a package that exists nowhere (a forward-looking proposal) is left alone, so the gate applies uniformly across proposed/implemented/rejected.scripts/check-workspace-constraints.tsasserts thepackages/<group>/<pkg>shape: group dirs carry nopackage.json, and no package sits flat at the root or nests deeper. Group names stay open — a new group may be added without editing the gate; only the depth-2 shape is fixed.
Alternatives considered
- A third tier (
adapters//impls/under each family) — rejected: uniform depth 2 keeps the workspace glob a cleanpackages/*/*and lets one@deepseek-ai/dsh-*tsconfig wildcard resolve every package. - Nesting persistence under
core/session/— rejected: the storage backends form a parallel capability family mirroringllm/andbash/, while the session log itself stays core product API. ui-stdiounderui/— rejected: it was example-coupled dev support, not a product surface.
Consequences
The restructure churned imports, workspace globs, doc links, build references, and package paths in one coordinated move. That churn is acceptable pre-release (per the AGENTS.md foundation-over-blast-radius stance) because it stops the flat layout from fossilizing support packages as product contracts, and it is a one-time cost: the wildcard paths, the glob-derived publint list, and the shape gate mean a new package needs no further structural edits.