Files
ss-tools/.agents/skills/self-orchestration/SKILL.md
busya a5764eb008 fix(orchestration): make KV-cache rules explicit; workers long-lived everywhere
- 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"
2026-08-18 16:24:19 +03:00

14 KiB
Raw Blame History

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_contracts to LOCATE a contract, and read / grep / glob to 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_context is a legal status (= INV_2 [NEED_CONTEXT]): the worker reports blindness instead of confabulating a dependency.
  • In workflow, encode the same contract via schema (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) is status: 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_goal and list_agents are NOT completion checks. Call get_goal only at a state boundary (to read or update my objective) and list_agents once 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_message it 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_agentsend_message. A direct send_message to 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/write are reserved for MY durable-memory files only (plans, ADRs, notes under docs/, 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-orchestration ONCE 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 35 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_message to 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-orchestration ONCE per task; never reload it.
  • A worker's persona / toolFilter / model are 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