# 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.