# dsh-ecc architecture
## 1. Architectural intent
dsh-ecc là plugin composition layer, không phải fork của DSH. Nó biến các
resource và command convention của ECC thành các capability mà DSH đã có
runtime seam cho phép:
- ECC agent markdown → normalized child persona/instructions;
- ECC SKILL.md → DSH SkillProvider;
- explicit model route + continuable child → spawn.
- explicit model route + completed-parent context → spawn_fork.
Các primitive này được nối trực tiếp với DSH runtime. Không có model-facing tool
call lồng bên trong tool mới.
## 2. Context diagram
~~~mermaid
flowchart LR
Host["DSH host/session"] --> Plugin["dsh-ecc plugin"]
Plugin --> Source["EccSourceManager"]
Plugin --> Agents["EccPersonaCatalog"]
Plugin --> Normalizer["PersonaNormalizer"]
Plugin --> Skills["EccSkillProvider + alias bridge"]
Plugin --> Spawn["spawn"]
Plugin --> SpawnFork["spawn_fork"]
Source --> Root["package assets or managed ECC revision"]
Root --> Agents
Root --> Skills
Root --> AgentFiles["agents/*.md"]
Root --> SkillFiles["skills/*/SKILL.md"]
Spawn --> LLM["ctx.llm"]
Spawn --> Subs["ctx.subagents"]
Spawn --> Binding["ChildSelectionStore"]
Binding --> Resume["registerContinuableSetup
+ installModelSelection"]
SpawnFork --> ForkBinding["ForkChildSelectionStore"]
ForkBinding --> ForkRuntime["ctx.subagents provider: fork"]
~~~
### 2.1 Layout package đã cài
Profile của DSH là ranh giới cài đặt. DSH không dùng một thư mục global
`.dsh/plugins`. Với một profile đang hoạt động, DSH resolve package root logic
từ:
~~~text
/profiles//package.json
└─ dependency: @staavanothanh/dsh-ecc
└─ assets/ecc/{agents,skills}
~~~
Store pnpm hoặc đích symlink vật lý có thể khác. Plugin phải lấy root từ URL của
module/package đã cài và chỉ expose revision bất biến trong `assets/ecc` cho các
adapter. `EccPersonaCatalog` đọc agents và `EccSkillProvider` đọc skills.
Không component nào copy file vào DSH
`.agent-presets` hoặc global skill directory.
### 2.2 Topology phân phối
Đường cài đặt cho cộng đồng dùng package dependency, không dùng repository
checkout:
~~~mermaid
flowchart LR
Release["GitHub release tarball staavanothanh/dsh-ecc"] --> Pnpm["dsh plugin ... add tarball"]
Pnpm --> Profile["package.json của DSH profile"]
Profile --> Bundle["dsh-ecc dsh.bundle.patch"]
Bundle --> Runtime["runtime dsh-ecc đã build"]
Runtime --> Assets["revision ECC được pin trong assets/ecc"]
Runtime --> Bridge["adapter agents / skills / model catalog"]
~~~
Package phải chứa `dist`, `assets/ecc`, `cordis.patch.yml` và license notice.
Runtime không được gọi ngược về checkout của tác giả, suy ra root từ `cwd` hoặc
fetch ECC `main`. Cài bằng `file:` chỉ dành cho development. Release phải vượt
qua smoke test trên một `DSH_HOME` tạm và sạch.
## 3. Plugin composition
### 3.1 Required runtime dependencies
| Dependency | Purpose |
| --- | --- |
| DSH plugin/context API | register tool, lifecycle, settings and disposal |
| ctx.llm | resolve model metadata and validate call config |
| ctx.subagents | continuable child start/follow-up and setup registration |
| DSH skill runtime | provider registration, catalog and agent/pre-step |
dsh-ecc không phụ thuộc vào tool-subagent hoặc workflow worker để tạo child.
Nếu một package export cần thiết là private, implementation phải tạo một
compatibility module có test thay vì import deep internal path không ổn định.
### 3.2 Component responsibilities
~~~text
dsh-ecc/
config/
schema # eccSource, transportProvider, persona policy
source/
root-resolver # DSH-resolved package root -> asset root
frontmatter # shared safe markdown parser
manager # bundled revision, pinned remote update, atomic swap
agents/
catalog # discover/parse ECC persona definitions
persona # resolve persona id and immutable content snapshot
persona-normalizer # strip upstream tools/model metadata
skills/
provider # SkillProvider implementation
namespace-bridge # planned /ecc:name client/host compatibility
spawn/
tool # spawn schema and execution
binding-store # in-process binding cache
durable-resolver # DSH live/persisted descriptor recovery
continuation-setup # fresh/cold-resume setup contribution
spawn-fork/
tool # spawn_fork schema and execution
fork-binding-store # isolated fork binding cache
fork-provider # DSH provider: fork
plugin.ts
package/
manifest # package.json, exports, dsh.bundle.patch
release-check # npm pack contents + clean DSH_HOME smoke test
~~~
Mỗi component có một boundary rõ ràng. File parser không được biết DSH context;
runtime adapter không được tự scan filesystem ngoài EccSourceManager.
EccSourceManager trả về một immutable revision root. MVP chỉ dùng revision đã
nằm trong `/assets/ecc`; remote update là phase
hậu MVP, tải ref/commit pin vào `/cache/dsh-ecc/ecc/`, verify
integrity, rồi đổi revision pointer atomically. Update không được xóa revision
đang phục vụ child cũ. DSH profile resolver là nguồn package root duy nhất; không
hard-code `cwd`, `.dsh/plugins` hoặc một absolute path của máy tác giả.
Installing dsh-ecc luôn mount đầy đủ agents và skills. Không có toggle runtime
riêng cho từng surface; muốn tắt tích hợp thì remove plugin khỏi profile.
## 4. Data model
### 4.1 Trusted root
~~~ts
type EccRoot = {
packageRoot: string;
absolutePath: string;
agentsDir: string;
skillsDir: string;
revision: string;
};
~~~
`packageRoot` là directory của package dsh-ecc do DSH resolve từ active profile;
`absolutePath` là bundled/managed ECC asset root. Resolver kiểm tra cả hai path
tồn tại, là directory, không chứa traversal do input runtime. Config được
normalize một lần; các component chỉ nhận EccRoot, không nhận raw path từ user
prompt.
### 4.2 Persona definition
~~~ts
type EccPersonaDefinition = {
name: string;
description: string;
persona: string;
sourcePath: string;
contentHash: string;
};
~~~
`PersonaNormalizer` loại bỏ `tools` và `model` khỏi frontmatter trước khi tạo
runtime persona. `spawn` nhận provider/model explicit và effort có điều kiện từ request;
child kế thừa effective tool scope của parent qua DSH composition. Persona
injection và cold-resume setup tham chiếu cách hai repo
`dsh-background-agents` và `dsh-agent-teams` đóng góp context/selection vào child;
DSH native descriptor vẫn là nguồn persist chính.
### 4.3 Child binding
~~~ts
type ChildBinding = {
childId: string;
label: string;
transportProvider: string;
selection: {
provider: string;
model: string;
reasoningEffort?: string;
};
persona?: string;
personaHash?: string;
createdAt: string;
schemaVersion: 1;
};
~~~
Binding store là state của plugin, không phải DSH global settings. Lifecycle và
key lookup tham chiếu pattern của `dsh-background-agents` và
`dsh-agent-teams`: hỗ trợ child id và label đã namespace `dsh-ecc:`. Writes
atomic; schema version cho phép migrate mà không làm hỏng cold resume. Hai repo
chỉ là blueprint để adapt, không được thêm thành runtime dependency.
`spawn` và `spawn_fork` có binding store tách biệt dù cùng schema. Fork binding
luôn ghi `transportProvider: "fork"`; resolver phải kiểm tra transport trước khi
hydrate cache để một follow-up không thể nhầm sang child của tool còn lại.
## 5. Spawn runtime
### 5.1 Fresh start sequence
~~~mermaid
sequenceDiagram
participant M as Main agent
participant T as spawn
participant L as ctx.llm
participant A as AgentCatalog
participant S as ChildSelectionStore
participant R as ctx.subagents
participant C as Child
M->>T: prompt, provider, model, effort?, persona?
T->>L: resolveModelInfo(provider, model)
L-->>T: model metadata + reasoningEfforts
T->>L: resolveCallConfig(selection)
L-->>T: canonical call config
T->>A: resolve optional persona id
A-->>T: persona body + metadata
T->>A: normalize tools/model metadata away
T->>R: getProvider(transportProvider)
T->>S: reserve binding(selection, persona)
T->>R: startContinuable(request.persona)
R-->>T: childId
T->>S: commit binding(childId)
T-->>M: structured result + child handle
R->>C: initial prompt with child-scoped selection
~~~
Binding nên được reserve trước start để child callback có thể lookup theo label;
commit child id sau khi DSH trả kết quả. Nếu start fail, reservation phải rollback
hoặc đánh dấu failed và không được tái sử dụng.
### 5.2 Fork seed và selection installation
`spawn_fork` chạy cùng các bước route/persona/effort có điều kiện của sequence trên nhưng
gọi provider `fork`. DSH provider tạo seed từ completed parent turns qua
`completedTurnPrefix`; event của parent turn đang in-flight bị loại khỏi seed.
Child vẫn nhận `parent` hiện tại để DSH compose tool scope và nhận cùng
`registerContinuableSetup` để cài provider/model/effort có điều kiện. Vì fork và spawn dùng
binding store/resolver riêng, cold-resume giữ đúng transport.
### 5.3 Selection installation
ctx.subagents.registerContinuableSetup đăng ký contribution một lần khi plugin
mount. Contribution thực hiện:
1. đọc `childCtx.agent.options` (provider/model và field host-local tùy chọn
`reasoningEffort`);
2. nếu là cold-resume, đọc effort đã nằm trong `session.requestHeader()`;
3. trả `installModelSelection({provider, model, reasoningEffort?})`;
4. nếu child chưa có request header và có effort, seed một header tối thiểu để
effort không mất khi process dừng trước request đầu tiên;
5. không cần cài lại persona: DSH descriptor đã persist persona và selection;
child tiếp tục dùng tool scope được compose từ parent.
Đây là điểm bảo đảm effort không mất khi child cold-resume. Không dùng parent
session selection sau khi binding đã có.
### 5.4 Follow-up
Đường chính là host control surface gọi `ctx.subagents.followup(childId,
prompt)`. Plugin không điều khiển vòng chat bằng cách gọi tool khác. Nếu
host/UI không expose control surface, cùng tool `spawn` dùng
`operation: "followup"` với `childId` như fallback; fallback chỉ gọi runtime API.
Khi follow-up fail, trả error phân biệt RESUME_BINDING_MISSING, MODEL_NOT_FOUND
và provider I/O để host quyết định retry. Nếu cả hai đường unavailable,
continuation không được báo là enabled.
### 5.5 Persona normalization
`PersonaNormalizer` là bước thuần, không ghi đè asset upstream:
1. parse Markdown frontmatter/body;
2. giữ `name` và `description`;
3. loại bỏ `tools` và `model` khỏi bản frontmatter runtime;
4. giữ body và content hash của file gốc;
5. tạo child request chỉ với persona body và selection explicit.
Khi request không có `toolFilter`, DSH compose child từ parent. Restrictions của
parent và host vẫn được áp dụng; normalizer không grant thêm quyền. Continuable
in-process child nhận các contribution native như `report` theo host runtime.
### 5.6 Implementation checkpoint
Runtime đăng ký hai model-facing tool: `spawn` dùng provider cấu hình cho
continuable child thường; `spawn_fork` dùng provider `fork` và binding riêng để
seed context parent mà không gọi nested `subagent_fork`.
Lớp runtime hiện đã nối các seam public mà DSH cung cấp:
- `ctx.tools.register(defineTool(...))` đăng ký `spawn`;
- `ctx.skills.registerProvider(...)` đăng ký catalog ECC với namespace provider
`dsh-ecc` và id invocation `/ecc:`;
- `ctx.subagents.registerContinuableSetup(...)` gọi
`installModelSelection` cho child context mới hoặc cold-resume;
- `ctx.subagents.followup(parent, childId, content, options)` được gọi trực tiếp
với đúng parent agent, không gọi lại một model-facing tool khác.
DSH `AgentOptions` công khai hiện chỉ khai báo provider/model. Adapter truyền thêm
`reasoningEffort` như một field host-local (runtime DSH vẫn bảo toàn field này),
đồng thời seed `request/header` để effort còn lại khi child được resume trước khi
thực hiện request đầu tiên. Đây là adapter behavior, không sửa core DSH.
`InMemoryChildBindingStore` vẫn là cache nóng để tránh đọc log ở mỗi follow-up.
Trên DSH rc1, resolver tùy chọn đọc live child trước, sau đó dùng
`ctx.sessionPersistence.inspect(childId)` để khôi phục descriptor và request
header. Resolver kiểm tra parent ownership, mode continuable, transport provider,
provider/model và effort nếu có, cùng persona, trước khi hydrate cache; không có fallback sang
selection khác. Host không expose persistence thì behavior cũ vẫn an toàn:
follow-up thiếu binding trả `RESUME_BINDING_MISSING`.
## 6. Skills runtime
~~~mermaid
flowchart TD
File["ECC skills//SKILL.md"] --> Parse["frontmatter parser"]
Parse --> Def["EccSkillDefinition"]
Def --> Reg["ctx.skills.registerProvider"]
Reg --> Cat["DSH skill catalog"]
User["User: /name (native flat entrypoint)"] --> Reg
Model["Model sees digest"] --> Tool["DSH skill loader"]
Tool --> Reg
~~~
Provider cần watcher/invalidation để skill mới hoặc file sửa được thấy trong
session hiện tại. Catalog chỉ chứa metadata; full body chỉ load khi explicit
user/model invocation. DSH rc1 hiện có flat skill candidate name và không nhận
dấu `:` trong candidate key, nên runtime 0.7.0 expose ECC skills dưới `/name`.
Namespace `/ecc:` vẫn là compatibility target; cần client/host alias bridge
riêng trước khi có thể tuyên bố namespace này hoạt động.
Nếu host đã mount official filesystem provider, dsh-ecc vẫn giữ provider name
riêng và precedence thấp hơn project/runtime provider. Không ghi đè definition
native có cùng canonical name.
## 7. Settings, hot reload và ownership
DSH settings là capability injection:
~~~text
ctx.settings -> plugin config and watchers
ctx.llm -> live route/model/effort capability
binding store -> immutable child selection snapshot
~~~
Khi reasoningEfforts thay đổi, request mới có thể đổi capability nhưng binding đã
tạo không tự đổi effort. Resume revalidate snapshot; nếu không còn supported,
resume fail rõ ràng. Đây là behavior an toàn hơn việc silently chuyển sang effort
khác.
Plugin config update phải tạo object mới/atomic snapshot; không mutate binding đang
chạy. Child đã start giữ transport provider cũ cho tới khi kết thúc.
## 8. Error isolation và observability
Mỗi boundary ghi structured diagnostic:
- component (root, persona, skill, spawn, resume);
- operation và input identifier đã redact;
- error code;
- retryable/stop condition;
- child id/label nếu đã tồn tại;
- duration và provider route (không log secret/token).
Runtime lỗi không được làm mất binding child. Spawn validation lỗi không được tạo
orphan child. Dispose phải dừng watcher, unregister provider/tool/listeners và
không xóa durable binding của child còn sống.
## 9. Trade-offs và rủi ro
| Quyết định | Lợi ích | Rủi ro/mitigation |
| --- | --- | --- |
| Chỉ expose /ecc: | tránh collision với DSH/plugin khác | cần namespace pre-step; plain /name do native xử lý |
| Plugin-owned child binding | cold-resume deterministic | thêm persistence; schemaVersion + atomic writes |
| Explicit route fields required | không kế thừa nhầm model | verbose call; phase 2 có thể cho default opt-in |
| ECC agent là normalized persona | giữ role/workflow và dùng native tool scope | child kế thừa parent scope; parent/host restrictions vẫn áp dụng |
| Package-first distribution | one-command community install, pinned assets | release/build drift; `npm pack` + clean DSH_HOME smoke test |
| Không gọi nested model tools | runtime sạch, test được | phải expose host control surface cho follow-up |
## 10. Implementation phases
1. Foundation: package skeleton, config schema, root resolver, parser tests.
2. Skills: provider, watcher, catalog, `/ecc:` invocation.
3. Personas: catalog, trusted id resolution, content hash, frontmatter normalizer.
4. Spawn: route/effort validation, persona injection, continuable start, binding store,
registerContinuableSetup, cold-resume integration.
`spawn_fork` thêm fork provider seed từ completed parent turns và fork binding
resolver; không thêm nested model-facing tool call.
5. Packaging: compiled artifact, bundled ECC assets, `dsh.bundle.patch`,
registry/GitHub install path and license/NOTICE.
6. Verification: unit/integration/restart tests, package smoke test on clean
DSH_HOME, 80% coverage target, security review and no-core-diff check.
7. Post-MVP ECC sync: scheduled/tag-triggered revision update, regenerate the
bundled closure, run compatibility checks, publish dsh-ecc release, then
optionally enable verified remote revision updates with rollback.
Phase order cố ý đặt spawn sau source/persona provider vì child persona và skill
metadata phải có source adapter ổn định trước khi gắn vào runtime.