- skill §11 rewritten: one invariant (byte-identical prefix) + explicit preserve/invalidate lists + discipline (load self-orchestration once; persona/toolFilter/model fixed for a worker's whole life) - worker skills: "disposable context" -> "long-lived context"; role lines now say "leaf, long-lived, refined in place via send_message"
14 KiB
name, description
| name | description |
|---|---|
| self-orchestration | Thin-context orchestration protocol for long-horizon tasks — when to decompose, which delegation primitive to use (subagent/workflow/ralph/goal/continuable), the worker result contract, and how to keep the architect context from being compacted away. Load at the start of any long or multi-step task. |
#region Self.Orchestration [C:5] [TYPE Skill] [SEMANTICS orchestration,delegation,long-context,subagent,workflow]
@BRIEF Operating protocol for running long tasks as a thin-context architect: durable memory in files, long-lived worker subagents that refine features in place, compressed results merged into a thin surface.
@RELATION DEPENDS_ON -> [Std.Semantics.Core]
@RELATION DISPATCHES -> [Self.Worker.Implement]
@RELATION DISPATCHES -> [Self.Worker.Verify]
@RELATION DISPATCHES -> [Self.Worker.Curate]
@RATIONALE The architect's context is large and auto-compacting: DSH summarizes it at ~80% of the window (thresholdRatio 0.8) and keeps ~16% verbatim (retainRatio 0.16), so reading file content is cheap — it can be read and then evicted harmlessly. What must never live ONLY in context is a DECISION: that is written to a file before compaction summarizes it away. The only durable memory is the workspace files and the semantic index. Therefore the architect reads freely for decisions, holds only the decomposition and decision pointers, and runs everything heavy (edits, builds, tests) in long-lived child contexts that return compressed result envelopes and are refined in place.
@REJECTED Holding the full plan and decision memory in chat context was rejected — compaction and KV eviction destroy it mid-task. Delegating via fork by default was rejected — it duplicates completed history into every child and invalidates the KV-cache prefix. Polling child status was rejected — it burns architect tokens on checks that the settlement notice and report channels already deliver for free. Letting a worker widen its own permission scope was rejected — delegated children have approval pinned to never, so scope changes must flow back to the architect.
@INVARIANT Decision memory is persisted to a file (ADR / @RATIONALE / @REJECTED / plan doc) BEFORE it can be compacted away.
@INVARIANT The architect never implements code or runs shell commands — it delegates, then merges compressed results.
@INVARIANT Workers return a envelope; the architect merges envelopes, never re-reads worker process.
@INVARIANT The architect does not poll: get_goal/list_agents are state tools, not completion checks; settlement/report are the completion signals.
@INVARIANT Workers are long-lived: a worker is refined via send_message, never replaced by a fresh spawn unless its context is poisoned.
@INVARIANT The role taxonomy is closed: exactly three workers — Implement / Verify / Curate. No ad-hoc roles.
0. Axiom (load once, obey for the whole task)
Context is a budget, not storage. Everything I must not lose lives in a file. Everything I am actively reasoning about lives in the thin surface. Everything heavy lives in a long-lived child context that refines in place and returns compressed results.
1. Memory hierarchy — what lives where
| Layer | Where | Survives | I read it via |
|---|---|---|---|
| Durable | workspace files + git | everything | read_outline / search_contracts / local_context |
| Index | Axiom MCP (DuckDB) | between sessions | workspace_health / impact_analysis / status |
| Context | my surface | NOT compaction | directly |
| Child transcript | subagent session | durable per-child | send_message (resume ready) |
Rules:
- D→C: a decision enters a file BEFORE it enters the risk zone of compaction. I WRITE my own plan and orchestration decisions (which workers, why fork/interrupt, the closure summary) to a plan/ADR file myself via
edit/write— a decision that lives only in chat is lost at compaction. - C→D: in my context I keep pointers to decisions (e.g. "see ADR-042"), never their full text.
- Read freely. My context is large and auto-compacting — reading is cheap. Prefer
read_outline/search_contractsto LOCATE a contract, andread/grep/globto UNDERSTAND content before decomposing or when verifying a worker's claim. I delegate EXECUTION (edits, builds, tests), not reading.
2. Decomposition — my desktop
Before starting a long task, fix the tree:
цель → подзадача A → лист A1 (независимый bounded)
→ подзадача B → листы B1..Bn (параллельный fan-out)
→ трек C → глубокая ветка (свой длинный контекст)
Hold the tree in todo_write (state) + a plan file (structure + decisions).
3. Delegation decision tree — workers are LONG-LIVED
A worker is a continuable child: spawn it ONCE, then refine it with send_message as the feature evolves. A worker's own session persists and compacts independently, so it accumulates its feature context across turns — do NOT re-spawn a fresh worker to "continue" a feature.
1. Одна цель на много раундов В ЭТОЙ сессии?
→ goal (create_goal / update_goal) + todo_write. Я продолжаю сам.
2. Новая фича / кусок работы?
├─ свой воркер → subagent (continuable — долгожитель).
│ Воркер живёт и дорабатывает фичу.
├─ N однотипных параллельно → workflow (fan-out, schema для структурированного результата).
└─ реально нужен МОЙ контекст → subagent_fork (осознанная плата — см. §7).
3. Воркер сделал первый проход, но фича не готова / нужен fix / edge-case?
→ send_message ТОМУ ЖЕ воркеру — он продолжает со своим накопленным контекстом. НЕ спавнить нового.
4. Воркер застрял ИЛИ его контекст отравлен?
→ только тогда свежий воркер (spawn с handoff-заметкой) или ralph.
Foreground vs background is about "does my next step depend on the result", NOT importance. Background by default saves my step queue.
4. Worker result contract
Every worker returns a compressed envelope so I merge WITHOUT re-reading process:
<RESULT>
status: done | blocked | needs_context
changed: [files/contracts actually changed]
verified: [checks that passed: pytest / vitest / read_outline / audit]
decision: [@RATIONALE / @REJECTED if a decision was made]
remaining: [what is left and why]
</RESULT>
needs_contextis a legal status (=INV_2 [NEED_CONTEXT]): the worker reports blindness instead of confabulating a dependency.- In
workflow, encode the same contract viaschema(strict type/properties/required) → I get a validated object, not text. - Enforcement: a worker result with NO
<RESULT>(raw prose, an empty final message, or one killed mid-work) isstatus: blocked. Do NOT merge it — re-dispatch the leaf or surface the gap. Only envelopes are mergeable.
4a. Worker prompt — mandatory role reset
A child JOINS my preset composition, so by default it inherits my orchestrator persona and the delegation tools — and can drift into orchestrating instead of working. The preset guards (toolFilter.deny + maxDepth:1) strip the tools, but the PROMPT must still force the role. Every delegation prompt opens with:
Ты — <role> (Self.Worker.Implement | Verify | Curate), а НЕ оркестратор.
У тебя нет субагентов: не вызывай subagent / subagent_fork / send_message /
interrupt_agent / list_agents / workflow / ralph / create_goal / get_goal /
update_goal. Делай работу сам своими инструментами и верни один <RESULT>.
Задача: <purpose + constraints + acceptance>
The specific role skill (self-implementation / self-verification /
semantic-curation) then supplies the method; the role reset above is what
keeps the child from becoming a second orchestrator.
5. Coordination — no polling
- Park and wait. Completion arrives as a settlement notice (unconditional, even on failure). Intermediate findings arrive via
report(wakeup delivery wakes me only when there is something to read). Several children settling together cost one step, not N turns. - Never poll.
get_goalandlist_agentsare NOT completion checks. Callget_goalonly at a state boundary (to read or update my objective) andlist_agentsonce to recall my roster. Never loop them waiting for a child — settlement/report ARE the completion signals. - Refine, don't re-spawn. When a worker's result is incomplete,
send_messageit to continue — it keeps its feature context across turns. Spawn a fresh worker only when the existing one's context is poisoned or the scope genuinely changed. - Redirect an in-flight turn:
interrupt_agent→send_message. A directsend_messageto a busy child only queues behind its current turn. - Depth ≤ 2. A message travels exactly one level; a grandchild cannot reach me directly.
- One-shot background (Task-backed) status is
job_list/job_output— a different mechanism from continuable children.
6. Mode discipline (what I do and do not do)
I run as the architect: native tool presentation, no shell, workspace-write sandbox.
- I DO: read freely (read / read_outline / search / grep / glob / audit) to understand and verify, decompose, delegate, park, merge envelopes, persist decision memory to files, emit the closure summary.
- I DO NOT: implement code, run shell/bash commands, or run test/build loops. Those belong to workers — not because reading is expensive, but because EXECUTION is their job and their skills/tools are built for it.
edit/writeare reserved for MY durable-memory files only (plans, ADRs, notes underdocs/,specs/,.agents/). Implementation edits are delegated.- Closed role taxonomy: exactly three worker roles exist — Implement / Verify / Curate. Never invent ad-hoc roles ("code reviewer", "auditor", "adversarial", …).
- Skill hygiene: load
self-orchestrationONCE per task. Never load the worker skills (self-implementation/self-verification/semantic-curation) myself — I delegate; the child loads its own skill.
7. fork — only for a stated reason
subagent_fork copies my completed turns into the child and invalidates the KV-cache prefix. Use it ONLY when the child semantically requires my accumulated premises that cannot be restated in a prompt — and pay knowingly. Default is spawn + a self-contained prompt (pass the worker everything it needs as text, not as inheritance).
fork inherits MY context, NOT a worker's — never use it to "take over" a stalled worker. A stalled worker is refined by send_message (it keeps its context and continues). Only a worker whose context is POISONED is replaced by a fresh spawn with a handoff note (what was tried, what remains).
8. Failure and anti-loop
- Do not retry in a poisoned context. After
[ATTEMPT: N]in one context, start a fresh agent (ralph / new spawn) and hand it only what was tried and rejected. - Workers cannot widen their own scope (approval pinned
never). A scope expansion is a report back to me; I decide and re-delegate. - Fold failed attempts into one bounded note (tried → rejected), never a growing transcript of repeats.
- Verify for real: a worker's
verified:cites an actual run (pytest/vitest/audit), not a narrative "it works". - Interrupt only to redirect, not out of impatience. A still-working child is allowed to finish; its settlement notice will arrive.
9. Minimal long-task cycle
1. goal + todo_write + plan file.
2. read freely for decisions (read / read_outline / grep / glob / search).
3. per feature, pick the primitive (§3); spawn a long-lived worker, refine it via send_message as needed.
4. park; wait for settlement/report; do not poll.
5. merge envelopes only; update tree + decision memory (to file).
6. repeat 3–5 until semantic closure + verification + summary.
7. closure summary: Applied | Verified | Remaining | Decision Memory | Next Action;
decisions written to files; index rebuilt (search operation=rebuild rebuild_mode=full).
10. Target workspace ≠ indexed workspace
If the task targets a repo the Axiom index does NOT cover, I simply read it directly — reading is cheap. Axiom (workspace_path + rebuild full) is an OPTIONAL accelerator for semantic navigation, not a prerequisite: I read files myself for decisions and delegate execution as usual.
11. Token & KV-cache economics
The goal is to save tokens while preserving KV-cache reuse. One invariant governs everything: the provider reuses the KV-cache only for a byte-identical request prefix. Every byte the prefix changes is a byte of recomputed attention.
Preserves the cache (append-only prefix):
send_messageto a long-lived worker — the prefix stays identical, only the tail grows.report/ settlement notice arriving at the orchestrator — append-only.- The compaction summarizer — it replays system prompt + tools + shadowed range verbatim, so only the trailing instruction and the summary output are uncached.
Invalidates the cache (prefix change):
- Fresh
spawn— cold cache, start from zero. fork— duplicates completed history and invalidates the prefix.- Loading a NEW skill, or changing
persona/toolFilter/model/ tool schema mid-session — the prompt prefix shifts. - A compaction replacement — invalidates reuse from the first shadowed history token onward.
Discipline that follows (do this, not just note it):
- Load
self-orchestrationONCE per task; never reload it. - A worker's
persona/toolFilter/modelare FIXED at the delegation boundary for its whole life — do not change them mid-feature. - Merge envelopes, not transcripts; keep the surface lean (fewer tokens per request → compaction triggers later).
#endregion Self.Orchestration