pi-web-ui-internal-api-orchestration
Skill2 starsChanged 44 days agoMITView on GitHub
Drive real agent work through Pi Web UI's local Internal API from any directory, harness or voice assistant on the same host. Use to run a task on several models and compare, spin up child or sub-agent sessions, split work across runtimes, batch-score items, be woken when a child finishes, answer a child's question, or monitor and clean up sessions. Tell every child to follow `agent-os-child`; waiting: `long-horizon-waiting-strategies`. To prove a change to Pi Web UI itself works, use the repository's live-validation documentation.
In this repository41 other files it gives its agents.
- skills/web-asset-generator/CLAUDE.mdCLAUDE.md
- academic-writingSkill
- agent-os-childSkill
- agy-pSkill
- amazon-uk-scraperSkill
- artifact-variationsSkill
- audio-transcription-workflowSkill
- capture-dashboardSkill
- claude-channelsSkill
- claude-pSkill
- code-documenterSkill
- context7-docsSkill
- context7-searchSkill
- deep-researchSkill
- docx-draftSkill
- grill-meSkill
- handoffSkill
- long-horizon-waiting-strategiesSkill
- openapi-schemaSkill
- opencode-pluginSkill
- pi-extensionSkill
- pptx-draftSkill
- saas-idea-finderSkill
- scraping-redditSkill
- scraping-twitterSkill
- secret-scanningSkill
- socialmedia-optimizationSkill
- systematic-debuggingSkill
- test-driven-developmentSkill
- text-to-speechSkill
- thematic-analysisSkill
- transcribe-audioSkill
- video-deep-understandingSkill
- video-useSkill
- web-asset-generatorSkill
- web-crawlingSkill
- web-create-assetsSkill
- web-data-acquisitionSkill
- webfetch-skillSkill
- youtube-captionSkill
- youtube-searchSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
26 rules. About 0 are common, 14 are distinctive, and 12 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈14
Distinctive · estimate
This author's own choices. Worth a look.
12
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 82
Contract version: Every feature note below names the Internal API contract version that introduced it.
- Very specific: line 119
Fallback: raw requests: Use them for anything the client does not cover (adoption, transfers, diagnostics), or when pi-orch is unavailable.
- Very specific: line 141
When you run inside a Pi Web UI-managed session, PI_WEB_UI_SESSION_ID (contract 1.47.0.
- Distinctive: line 177
retained and disabled: Do not try to route through it.
- Distinctive: line 181
Ask the server what exists right now.
- Distinctive: line 212
Read actual tasks, reservations and the limiting reason—not just the nominal turn ceiling.
- Very specific: line 266
Never use a remembered model list, a family-name match, a coarse reasoning flag, a stale contract version, or a guessed fallback.
- Very specific: line 284
If the Internal API persistently refuses admission (e.g.
- Distinctive: line 290
Treat a dispatch route as the complete tuple:
- Distinctive: line 384
Prefer a single create when atomic retention matters — batch creation does not guarantee leases.
- Very specific: line 406
The create response's model echo is not proof of binding: Verify the effective, provider-qualified model via /info (and for Pi, the last …
- Very specific: line 454
Every successful response and receipt carries dispatchMode, the operation *actually* performed, alongside the requested mode.
- Very specific: line 492
Put the directive in the prompt itself, not only in a brief file it points to, and restate it inside a goal objective ("follow …
- Distinctive: line 503
The RED method: test first; no git stash (its stack is shared across worktrees).
- Distinctive: line 504
Restarts stay with you: never delegate a restart of the service that hosts the child.
- Distinctive: line 555
After an implementer hands back, and before you accept or merge, dispatch a fresh session to review the change.
- Distinctive: line 562
Read-only is enforced by the brief only: Check git status is clean afterwards.
- Very specific: line 575
Who are you? Decide this BEFORE any detached dispatch: Only a *managed* session (one Pi Web UI created) can be an onFire target.
- Very specific: line 626
202is queue acceptance, not delivery: Never report "notified" from it. - Very specific: line 654
Keep the parent goal running while independent work remains.
- Distinctive: line 658
Verify the goal engine version you rely on is actually loaded.
- Very specific: line 784
After positive quiescence, release_retention only the exact lease you own (payload key retentionLeaseId).
- Distinctive: line 792
If release or deletion is unconfirmed, preserve that uncertainty in the handoff.
- Distinctive: line 811
Sequence gating is yours, not the operator's: When an earlier lane lands and the tree is clear, un-gate the queued lane at once.
- Distinctive: line 847
Run receipts are dispatch-scoped.
- Very specific: line 871
If the repo is not checked out, this skill plus live /capabilities is enough.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Pi Web UI Internal API Orchestration
Pi Web UI is a browser interface and backend sitting in front of several agent runtimes. The part that matters here is that it exposes a local-only Internal API over a Unix socket, which turns it into an orchestration control plane you can drive from anywhere on the machine — a different repo, a terminal, a script, a voice assistant — without being inside the web UI at all. Sessions you create land in the same registry the browser uses, so the operator can open them later.
When to use
Also: score or classify batch items on a model, hand a job to another model, start something and check back later, transfer context between sessions, adopt a session as a child, automate Pi Web UI without the browser, and manage children as a parent orchestrator (Unix-socket orchestration, session fan-out, runtime comparison, watch-wake).
Which skill do you want?
The two Pi Web UI skills split on one question: are you using Pi Web UI, or testing it?
| You are… | Skill | Default target |
| Dispatching real agent work — fan-out, comparison, a long task to check back on | this skill | the production socket |
| Proving a change to Pi Web UI or a runtime actually works | the repository's live-validation documentation | a disposable server, always |
That distinction decides the safety posture, so get it right before your first
call. Using production here is correct and expected — these sessions are meant
to be visible in the operator's Web UI. What is never acceptable is validating a
code change against production, or stopping, restarting, redeploying, or
reconfiguring the service that hosts your orchestration on your own initiative —
the one exception is a programme whose operator authorised a restart, run by the
parent under the protocol in references/orchestrator-governance.md. If the task
is "does my change work?", stop and read the live-validation docs.
How to read this skill
This is a reference, not a tutorial — read only what the current job needs, not everything. The usual path: Connect → Golden rule → Choosing the route once, then straight to the numbered step you're on.
This file holds the workflow and the rules that keep you out of trouble.
Tier 1 — the only must-read: references/parent-card.md (one page: the rules that
bit, copy-paste sequences per harness, on-wake triage, routing). Read it before you
dispatch anything and again after every compaction. Also read references/routing.md
before choosing a model, and mandate the agent-os-child skill in every
child prompt (§3; you do not follow it, you mandate it). Everything else is reference:
open a file only when the card or a numbered step points there.
| Reference | Open it when |
references/pi-orch.md | before your first pi-orch call (verbs, flags, exit codes) |
references/walk-away.md | before your first detached dispatch (conditions, fan-in) |
references/claude-code-orchestrator.md / antigravity-orchestrator.md | you orchestrate from that harness |
references/goals.md | arming a goal, or dispatching under your own goal |
references/evidence.md | before declaring a child complete |
references/sessions.md | binding a model, answering a child's question, identifying sessions |
references/adoption.md | adopting a session as a child |
references/orchestrator-governance.md | several children, restarts, defects |
references/multi-phase.md, programme-kit.md | a multi-wave programme (templates: assets/programme-kit/) |
references/patterns.md | designing a fan-out or reporting to the operator |
Where things are in this file:
| If you are… | Read |
| Making your first calls | Connect from anywhere, Golden rule: discover first |
| Picking runtimes/models/roles for children | Choosing the route → references/routing.md |
| Planning a programme (dependent lanes, shared repos, other lineages) | Planning a multi-phase programme → references/multi-phase.md |
| Creating, preparing, dispatching; writing the child brief | §1–§3 (child directive and brief: §3) |
| Dispatching an independent reviewer child | §3c → references/multi-phase.md (The reviewer child) |
| Dispatching long work and walking away; fan-in of several children | §4 → references/walk-away.md |
| Arming goals, or pausing your own goal to supervise | §4b–§4c → references/goals.md |
| A child question or approval; finding a session by any id | §5, §7 → references/sessions.md |
| Proving a child actually finished | §6 → references/evidence.md |
| Adopting an existing session as a child | §8 → references/adoption.md (operator CLI-exit gate first) |
| Transfers, usage totals, cleanup | §9 |
| Several children, production restarts, defects, notifications | §10 → references/orchestrator-governance.md |
Contract version. Every feature note below names the Internal API contract version
that introduced it. An additive minor release keeps existing shapes; if /capabilities
reports a different minor than your client's snapshot expects, read the contract's
exception table first — pi-orch also warns SNAPSHOT_STALE.
Fields and endpoints here are the working subset; the pi-web-ui repo's
docs/INTERNAL-API.md and docs/INTERNAL-API-CONTRACT.md are the authoritative
full reference — open them when a shape below doesn't answer your question.
Connect from anywhere
Preferred: the pi-orch client (public: https://github.com/valtterimelkko/pi-orch; pi-orch on
PATH is the bin of that repo). It builds every request against a drift-guarded contract
snapshot, sends X-Parent-Session for you when it knows your session id, and uses documented
exit codes. Its wait blocks in the server's long poll and never polls. Programmes dispatch
through it, not through hand-written curl scripts. Read references/pi-orch.md before your
first call: verbs, goal budget, retention ids, waiting patterns, route caps, exit codes.
SID=$(pi-orch spawn --runtime pi --cwd /work/dir --model-selector <live-selector> \
--thinking low --owner <you> --ttl 3600 --id-only) # + --goal-objective "…" --goal-budget-tokens N for a goal child
RID=$(pi-orch prompt "$SID" --message "…" --id-only) # detached, idempotency key built in
pi-orch wait "$SID" --run-id "$RID" --deadline 1800 --json # in-turn wait only; an idle parent registers a watch and ends the turn (§4)
pi-orch result "$RID" --json # final text + parsed completion block
pi-orch verify "$SID" --run-id "$RID" --since <base> [--rerun "npm test"] --json # check the block's claims
pi-orch status --parent <you> --json; pi-orch cleanup "$SID" --lease <id> --owner <you>Rules worth carrying: a goal child that may run long needs --goal-budget-tokens (the 5,000,000
default pauses it mid-run) and a single-line objective; a Claude Code parent names its children
orch-<programme>-<parentShort8>-<lane> because it has no managed parent identity; exit 3 is a
deadline (re-wait, never re-dispatch), 18 means reconcile with status before any re-spawn,
25 means the route cap refused the spawn (nothing was created); the completion block is a
claim, so run verify before accepting it. Per-route caps: spread a large wave across routes
rather than queueing it on one (one provider route stops completing above roughly five to eight
concurrent children).
Fallback: raw requests. Use them for anything the client does not cover
(adoption, transfers, diagnostics), or when pi-orch is
unavailable. Nothing here assumes a working directory. The socket and token are
absolute paths, overridable by environment variable so the same snippet works in any
harness:
SOCKET="${PI_WEB_UI_SOCKET:-$HOME/.pi-web-ui/internal-api.sock}"
TOKEN_PATH="${PI_WEB_UI_TOKEN_PATH:-$HOME/.pi-web-ui/internal-api-token}"
TOKEN="$(cat "$TOKEN_PATH")"
API_BASE="${PI_WEB_UI_API_BASE:-http://localhost/api/v1}"
SELF_ID="${PI_WEB_UI_SESSION_ID:-${PI_SESSION_ID:-}}" # your own internal id, when you are a managed session
api() {
curl --silent --show-error --unix-socket "$SOCKET" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
${SELF_ID:+-H "X-Parent-Session: $SELF_ID"} \
"$@"
}When you run inside a Pi Web UI-managed session, PI_WEB_UI_SESSION_ID (contract
1.47.0; Pi also keeps PI_SESSION_ID) is set automatically, and the runtime also
exposes PI_WEB_UI_SESSION_ORIGIN and PI_WEB_UI_PARENT_SESSION_ID (OpenCode and the
Claude channel backend get none). From a bare CLI there is no internal id: leave
SELF_ID empty and omit the header rather than inventing one. It is optional
identification, not auth.
Parent linkage (contract 1.34.0): the X-Parent-Session header links the
calls you make to your session, so linkage events (child_dispatched,
child_turn_ended, watch_registered, watch_fired) are published to your
session's event stream and are watchable like any other event. Linked
children persist parentSessionId; GET /sessions/:id gains an additive
children list. Unresolvable values are ignored — and a native Claude Code
parent is not a Pi Web UI session, so its X-Parent-Session does not link.
From a native Claude parent, put $CLAUDE_CODE_SESSION_ID in the child
directive as the parent session ID (§3), and record the family in whatever
coordination surface you use, so peers can see which dispatch a child belongs
to. A session that already
exists — created earlier without linkage, or a native CLI session on disk —
can be linked under you after the fact instead of re-created; see §8 and
references/adoption.md.
Confirm Pi Web UI is actually present before planning anything:
ls -l "$SOCKET" "$TOKEN_PATH"Auth is Authorization: Bearer <token> over the socket. This is a trusted
same-host surface, not an RBAC boundary: any token holder can inspect and
control every session. So be explicit with the operator about target, runtime,
model, thinking level, session count, retention, and likely side effects before
acting, and never delete, abort, transfer, or repurpose sessions your
orchestration does not own.
The repository also ships an experimental seven-tool stdio MCP adapter, but it is retained and disabled. Do not try to route through it; use the socket.
Golden rule: discover first, every time
Ask the server what exists right now. Machines differ, backends change, and model lists are live.
GET $API_BASE/capabilities
GET $API_BASE/capacity
GET $API_BASE/models/capabilities— verifycontract.nameispi-web-ui-internal-apiand checkcontract.majorVersion. Capability-gate optional features rather than assuming them. The runtime feature flags are per-runtime, nested underruntimes.<runtime>(not top-level): readsupportsFollowUp,followUpSemantics,supportsSteer,supportsSteerWhileBusy,supportsThinkingLevel,supportsApprovals,supportsInteractiveQuestions,supportsReplayHistoryfrom the entry of the specific runtime you plan to use./capacity— a preflight, not a reservation. Prompt admission is rechecked at dispatch and can still return429 ADMISSION_CAPACITY_EXHAUSTEDfor slot/runtime limits, or503for resource pressure, withRetry-After. Resource refusals includeheap_pressureandevent_loop_lag(1.49.0;/capacityshowsheapandeventLoopLag), and503 SERVER_DRAININGwhile a deploy drains (1.51.0). Treat any429/503withRetry-Afteras "wait and retry" (a drain ends in a restart: re-check/capabilitiesafterwards), and never read the wire contract's code list as closed. Anevent_loop_lagrefusal usually means the host is overloaded (another agent's build or benchmark), not that your request is wrong:pi-orchwaits outRetry-Afterfor you; raw callers must too. Heavy work of your own belongs underniceand capped CPU and memory (systemd-run --scope -p CPUQuota=… -p MemoryMax=… -- nice -n 10 <cmd>; somesystemd-runversions reject-p Nice=10, so putniceinside the scope), so it cannot starve the server's single event loop. Read actual tasks, reservations and the limiting reason—not just the nominal turn ceiling. Admission permits are not a complete count of browser/native work or autonomous goal continuations; separate request receipts can also refer to one executing turn./models— choose exactly one live-advertised entry. Read itsid,provider,thinkingLevels, and for Claude profilesbackendandclaudeModel. A missing field is unknown, not permission to fill it in from memory. Profile ids are opaque strings; since contract 1.25.0 both/modelsprofile entries and/capabilities.claudeProfilescarry the same underlying alias inclaudeModel/model. After creating a session with an explicit model, trust only the create response'sresolvedModel/modelBindingfields (contract ≥ 1.25.0) or a follow-up/inforead-back — never assume the echo means the binding happened.- Watch mutation capability —
features.watchGenerationPreconditions, when present with the documented field names, enables opaque-generation conditional register/delete. Use observed tokens and exact ownership; absence means legacy non-CAS semantics, not permission to claim atomic deletion. Seewalk-away.md. - Provider quota —
/capacityis Pi Web UI's local admission capacity and/sessions/usageis our own recorded consumption; neither is the provider's remaining subscription quota. Check that before choosing a route: read each provider's remaining quota in its own dashboard or CLI (a zero-model-token, read-only check), including per-provider windows, resets, shared pools and any peak-window flag. That check is your authoritative pre-dispatch evidence.
Read enabled as well as available. Since contract 1.15.0 they mean
different things: enabled: false is an operator-disabled runtime, which is not
the same as uninstalled or unhealthy. Treat disabled as unavailable and never
silently substitute another runtime.
There are five runtime families, not four: pi, claude, opencode,
antigravity, and the feature-gated commandcode. Don't hard-code the old
four-runtime assumption.
Pi provider policy — on contract 1.16.0+,
capabilities.features.piProviderPolicy.blockedProviders lists Pi providers
barred from automation to prevent accidental metered spend. The shipped default is
empty: the direct openai provider and the full OpenRouter gateway catalogue are
both served through /models and selectable like any other provider (check
blockedProviders for the effective per-deployment list — operators can re-block
exact provider ids via INTERNAL_API_BLOCKED_PI_PROVIDERS, and a non-empty deployment
rejects blocked providers with 403 PROVIDER_NOT_ALLOWED). Don't guess around an
omitted model on deployments that do omit them. Being served is not being
authorised: pay-as-you-go providers (direct Google API, OpenRouter, a direct
anthropic API key, a native DeepSeek API, plain openai) need the operator's
direct authorisation before any use — see references/routing.md. A
subscription-backed provider is distinct from a metered direct provider and is
never covered by this policy.
If a model you expect is missing, refresh the catalogue rather than
improvising: POST $API_BASE/models/refresh with {"runtime":"pi"} (refreshes
the OpenRouter catalogue and returns an ids diff) or the default/"opencode"
mode (warms the models.dev cache and recycles the idle backend). It returns
public ids and metadata only, never credentials.
Never use a remembered model list, a family-name match, a coarse reasoning
flag, a stale contract version, or a guessed fallback. If the exact combination
you need is absent, stop and report why.
Choosing the child path
Internal API first for every child size: it keeps routing and quota control. Use the harness's own subagents (Agent tool) or a Claude Code cloud child only when the Internal API is unavailable or the operator asks.
| Path | Who pays | Wake | Coordination record |
| Internal API | subscription pools; metered only with operator leave | server watch | visible in Pi Web UI |
| Agent tool subagent | the harness's own plan | task result in your turn | none |
| Claude Code cloud child | separate cloud credits | none on a clean finish (see below) | none |
- Agent-tool
isolation:"worktree"can lose its context (every Bash refused): create the worktree yourself and tell the child tocdinto it, using absolute paths. - Cloud children bill credits your provider dashboards do not show. A clean finish emits nothing watchable, so give each lane a branch, end every child turn with a pushed commit whose subject starts
QUESTION|BLOCKED|HANDBACK, run a cappedgit ls-remotewatcher as a background task in the dispatch turn (Bash timeout 7,200,000 ms; the 30 min default kills it) and arm a ≤ 60 minwake_deadlinethat checksget_session. - If the Internal API persistently refuses admission (e.g.
event_loop_lag), retry briefly, then use an Agent-tool child on a capable model as the fallback for the same bounded task — provided the operator has allowed that path.
Choosing the route
Treat a dispatch route as the complete tuple:
runtime + selector/id + provider + profile/backend/Claude-model (when used)
+ advertised thinking or discovered native effort + invocation roleNames are not selectors. Immediately before real dispatch, get one fresh
/models response, filter the named runtime list, and select exactly one entry
satisfying every predicate; use its returned selector (since contract 1.26.0
every entry carries one — the exact string POST /sessions accepts, so copy it
rather than constructing provider/id yourself). Zero matches, multiple
matches, a missing required field, or an absent required level is a refusal —
never a reason to take the first similar entry or another backend. Keep the
matched metadata with the dispatch record.
→ references/routing-rationale.md explains why a route table is shaped as it is (why a list price is the wrong yardstick, what to check before changing a row); read it before changing any route.
→ references/routing.md carries the method: choosing from live metadata, checking
provider headroom, the three work classes (Class 1 routine children on cheap,
high-quota models; Class 2 more demanding work; Class 3 the most demanding), the
PAYG rule (metered providers only with the operator's direct authorisation), the
reviewer-from-a-different-family rule, per-route concurrency caps, hard exclusions
and route-discipline rules. Read it before choosing a model — the exclusions and
quota lanes are the kind of thing that looks harmless to guess at and is not.
Planning a multi-phase programme
Most runs are one wave — dispatch, watch, fan in, done — and §1–§9 below cover that. When the work is a programme (a sequence of dependent improvements over multiple waves, often in shared repos alongside other active agents), plan the organisation before the first dispatch. Parents fail at the joins between waves, not inside them. Three rules carry the shape:
- Write the programme into three durable artefacts that survive parent
compaction and restarts: a stable strategy document (scope, waves,
ownership, gates; operator-gated status header
PLANNING ONLY→READY→EXECUTING; amendments recorded in place; records scope, not completion), a mutable live checkpoint re-read before acting (per-child status, receipts, wake-delivery ids, next sequence, honest counters), and a per-child brief + evidence directory (bounded outcome, owned paths, handback format with a FROZEN marker). - Brief every child with a bounded, self-contained packet: frozen success criteria, bounded files, exact test commands and guardrails, rather than dumping repo context. A child that already holds its context should not re-query or loop.
- Give every concurrent child an isolated git worktree with disjoint owned paths; the child states those paths (and what it leaves to others) in its hand-back, or in whatever coordination surface your setup provides (§3b).
- Derive waves from the sequence, not from available children: a parent-owned bootstrap wave first when the supervision machinery itself is suspect; parallel children only on disjoint owned paths; later phases gated on earlier interface stability or another lineage's work landing (postpone the seam, don't block); a fresh read-only reviewer child after each lane (§3c), not only at the end, and a named reviewer live re-run lane before the wave closes; code/test/deployment reported separately. Declare dependencies by what a step needs (interface, data, deploy order), and put a design gate in the brief of any behaviour-critical lane.
- Acceptance is a verdict, not a receipt: the parent independently re-runs verifiers and writes its own probes; every defect returns with RED evidence as one bounded correction brief; accepted phases freeze.
→ references/multi-phase.md has the full pattern: the programme trigger
table, the three artefacts in detail, wave derivation, ownership contracts for
concurrent writers, correction cycles, and cross-wave supervision;
references/programme-kit.md has the templates and runbooks to copy. Read it
before dispatching the first child of anything longer than one wave.
The orchestration loop
1. Create sessions
POST /sessions for one child, POST /sessions/batch for several. For anything
long-lived, ask for retention atomically at creation so a failure can't leave
an orphan:
{
"runtime": "claude",
"model": "profile:<discovery-resolved-id>", // SDK profiles only, see below
"thinkingLevel": "max",
"cwd": "/approved/working/directory",
"retention": { "mode": "durable", "ttlSeconds": 3600, "ownerId": "<retention-id>" }
}Claude on the Internal API is SDK-only (contract 1.46.0): /models?runtime=claude
lists only SDK profiles, and a create or prompt through any other Claude backend fails
403 CLAUDE_BACKEND_NOT_ALLOWED (features.claudeBackendPolicy in /capabilities; it
also covers watch wake, goal dispatch and batch). Take the exact profile:<id> from the
live /models entry and do not construct one.
Prefer a single create when atomic retention matters — batch creation does not
guarantee leases. Record at minimum the canonical sessionId, the exact route
tuple, the invocation role, your ownerId, the returned retention.leaseId, and
your own orchestration id. Since contract 1.34.0 the create response echoes
parentSessionId when you identified yourself via X-Parent-Session, and
GET /sessions/:id lists linked children. Since 1.54.0 every create also records
parentSource (header | body | bash | peer): without the header the server
resolves the calling session from socket peer credentials, fails closed (no link)
on any ambiguity, and costs a slower create, so still send X-Parent-Session
whenever you have an id. GET /sessions?parent=<id> lists a parent's children
(404 for an unknown parent). There is still no orchestrationId, so keep your own
tree record too.
Create into a directory that exists (1.53.0): every create checks the effective
cwd (exists, is a directory, writable) and refuses 400 PREFLIGHT_FAILED with a
failures[] list before any model token is spent, so make the child's worktree
first. Add "preflight": {"paths": [...], "tools": [...]} (absolute paths, bare tool
names; also accepted on POST /sessions/:id/prompt) for the files and tools the
brief depends on: a bad workspace then fails in one cheap round trip instead of
many children discovering it mid-task. Batch create answers 200 with
per-entry PREFLIGHT_FAILED. Treat it as fix-then-resend, never blind retry.
The create response's model echo is not proof of binding. Verify the
effective, provider-qualified model via /info (and for Pi, the last
model_change entry on disk) before spending a long run — a silent rebind to a
much costlier model has happened with a correct-looking echo.
→ references/sessions.md (Model binding) has the verification reads, the
Claude-profile rules, and the observed failure shape.
Bindings are durable (contract 1.33.0) — the run receipt is authoritative. After
dispatch read servedModel and modelRebound on the receipt; a binding that cannot be
applied fails loudly (MODEL_NOT_APPLIED / PROVIDER_NOT_ALLOWED). Never re-verify by
hand-scraping child session files during a run (references/sessions.md).
2. Prepare, if needed
Model, thinking-level and effort switches, and retention leases, go through
POST /sessions/:id/control (set_model, set_thinking_level, set_effort,
acquire/renew/release_retention). Thinking levels are capability-gated; for
Pi the response level is the effective read-back after SDK clamping, not an
echo. set_effort is the Command Code axis — idle-only, next-turn, never
changes thinkingLevel (models with no adjustable effort take no effort
request). For Claude, pick the backend/provider profile at session creation
rather than switching later; backend comparisons are cleaner as one session per
profile.
→ references/sessions.md (Control actions) has the exact action payloads.
3. Dispatch
POST /sessions/:id/prompt, POST /sessions/batch/prompt.
| Field | Notes |
message | required |
verbosity | answers (default, non-streaming, final text only), tasks (SSE status headlines), full (SSE everything) |
mode | prompt, follow_up, or steer — see below |
detach | fire-and-forget; only valid with verbosity=answers, otherwise 400 |
idempotencyKey | session-scoped, 1–128 chars. A matching retry reuses the run; a different payload on a live key returns IDEMPOTENCY_KEY_CONFLICT |
requireActiveTurn | for follow_up, forbid idle promotion |
Modes — this is how you redirect a child that's already working, and it's easy to miss:
| Mode | Meaning | Pi | Claude | OpenCode / Antigravity |
prompt | start a new turn | 409 SESSION_BUSY if busy. From 1.45.0: a swallowed prompt (extension input-hook swallow / fence) fails fast as PROMPT_NOT_EXECUTED after a 2 s grace; a session whose lease a live foreign runtime holds refuses up front as 409 SESSION_OWNED_BY_OTHER_RUNTIME (ownerPid/ownerMode, no run created); a dead lease holder is auto-recovered (dispose→rehydrate, pins kept) before dispatch | 409 SESSION_BUSY if running | 409 SESSION_BUSY if running |
follow_up | deliver after the current turn | busy with a live turn → queued (receipt queued); idle → promoted to a new turn with dispatchMode:"prompt"; busy with no live turn (auto-compaction) → 409 SESSION_BUSY + Retry-After (1.57.0). Pi "busy" (1.57.0) = manager busy/streaming, or sdkStreaming, or compacting: the same predicate drives prompt refusals, busy on GET /sessions/:id, and watch settlement | no queue: running → 409 SESSION_BUSY + Retry-After; idle → new turn | OpenCode: same as Claude. Antigravity (contract 1.37.0): busy → queued — the write goes into the live agy stdin stream and runs as the next turn |
steer | join the active turn | requires an active turn; idle → 409 SESSION_NOT_STREAMING | SDK backend only (backendMode:"sdk", contract 1.29.0): delivered at the next tool boundary without interrupting; the receipt completes when the joined turn ends and the response carries the continuation text. Idle → 409 SESSION_NOT_STREAMING; non-SDK backends → UNSUPPORTED_OPERATION | OpenCode → UNSUPPORTED_OPERATION; Antigravity → 409 SESSION_BUSY (no mid-run join in the agy stdin protocol — use follow_up, which queues natively) |
Every successful response and receipt carries dispatchMode, the operation
actually performed, alongside the requested mode. requireActiveTurn: true
turns idle promotion into 409 SESSION_NOT_STREAMING everywhere. Capability-gate
the differences with followUpSemantics and supportsSteerWhileBusy — steering
is a Pi capability and, since contract 1.29.0, a Claude SDK-backend capability
(runtimes.claude.supportsSteer).
A busy steer is not admission-checked (it joins an existing turn), so steering
a busy parent works even when every slot is full; new turns can still get 429/503
with Retry-After — detail in references/sessions.md (Dispatch modes and admission).
Persist the returned runId before doing more work.
For anything that may outlive your connection, use detached dispatch. An
attached tasks/full stream disconnect cancels its run; use
verbosity=answers with detach:true for disconnect-safe execution.
A separate /events observer does not own the execution. Repaired SSE helpers
close slow/oversized responses at their buffer limit, so streaming is not a
substitute for durable receipts:
POST /sessions/:id/prompt {"message":"…","verbosity":"answers","detach":true} → 202 + runId
GET /runs/:runIdLarge or slow fan-outs should prefer detached individual prompts, because
batch/prompt holds answer bodies until its response completes.
Mandatory child skill requirement. The agent-os-child skill is **not injected
into API-created sessions by any harness; only your prompt delivers it. Every child prompt,
for implementers, researchers and reviewers alike, must carry this directive. Without it a
worker drifts into narrative, omits command exit statuses, ignores exclusions and
self-signs-off:
"You are dispatched as an Agent OS child worker. You MUST load and strictly follow the
agent-os-childskill. Your session ID is<sessionId>. Your parent session ID is<parentSessionId>. Work strictly inside execution worktree<cwd>. Owned paths:<paths>. Negative exclusions:<paths>. Hand-back directory:<dir>. Success criteria are frozen. Announce your task once before editing files (a one-line note in the hand-back directory), and leave a closing note when you finish. <Push / do not push.> Hand back by ending your turn only when your own commands have finished; never wait or poll."
The directive pi-orch lane run sends adds "declare presence on the board" clauses for the author's coordination layer. If you have no such layer, drop those clauses or leave them in: the child skill tells a child without a board to write the note file instead.
Put the directive in the prompt itself, not only in a brief file it points to, and restate
it inside a goal objective ("follow agent-os-child; mark the goal achieved and end") —
goal-engine dispatches that dropped it produced most of the unmandated children in an
audit of real child sessions. The rest of a good brief (each item traces to a
recurring failure in that audit):
- Its own worktree for every concurrent child — never two children in one main checkout.
- Push stance. Say whether the child may push. Lane branches are often never pushed (children commit only; the parent merges) — state the rule anyway.
- The long commands and how to run them (foreground with a timeout), and that a child
must not end its turn while its own job runs — or must start the message
NOT FINISHED:. - The RED method: test first; no
git stash(its stack is shared across worktrees). - Restarts stay with you: never delegate a restart of the service that hosts the child.
- Read-only roles: say "no edits, no stash, no extra turns" (lookups are fine); add "no lookups" only for a genuine fresh-reader or blind test.
- A budget for research children (time or tool calls), a checkpoint cadence and a hand-back milestone — unbounded research children ran for thousands of tool calls.
An end-of-task report block (contract 1.58.0), so the server captures the child's claims (§6).
pi-orchappends it for you (for a goal child also theStatus:marker and field-shape instructions); paste it by hand only for raw dispatch, verbatim inside this four-backtick fence (a cheap fast model followed it 16 times out of 16 in live trials):END-OF-TASK REPORT (required): the LAST thing in your final answer must be exactly this kind of fenced block (info string `completion`, JSON body): ```completion {"schema":"pi-completion/v1","status":"done","summary":"<one line>","commands":[{"command":"<a command you ran>","exitCode":0}],"filesChanged":["<path>"]} ``` Fill in the real values; add "tests", "commits", "openIssues" or "blockedReason" fields only if they apply. Do not end your turn with only a tool call: after your final tool call, always write a short final answer that ends with the report block. Nothing after the closing fence.- Live proofs in the child's own disposable server copy only the credential of
the approved route into the isolated agent directory, count
models.jsonentries that carryapiKeyas credential copies and delete every copy afterwards, name the child route explicitly in any inner brief, and assert the served model by script. A parent holding a full credential copy can silently pick a different model for its children. - Host rules the brief must carry (binding wording in
programme-kit.md, common brief): disposable servers started with the inherited production placement or tool-root variables removed (env -u <VAR> …); heavy commands in asystemd-run --scopewith a hardMemoryMax; changed-file lint against the lane base; never touch the main checkout. The child-side form isagent-os-child§3 and §7. - Templates for all of this:
references/programme-kit.md.
For a read-only reviewer, replace the owned-paths, exclusions and push sentences with "You are a read-only reviewer: do not edit, commit, stash or push, and leave the tree untouched; numbered answer and correction files amend the brief", and add the verdict format (§3c), to be returned in the review file and the final message. Your own prompt already carries the identity and scope context, so the brief need not restate it.
3b. Task announcement: parent vs child
If you run a coordination layer (a shared task board, a sibling-agent registry, a worktree claim file), the child announces what it touches and the parent defines the boundaries and lineage:
- The child announces what it owns, once, before editing, and says what it leaves to
others; on finishing or blocking it leaves a closing note before handing back. If the
layer has a command for this, pass the child its
sessionIdfromPOST /sessionsso the announcement merges onto the existing linkage. Where the layer distinguishes an "exclude" list, note that it is information for peers, not a keep-out zone. - The parent defines the worktree, owned paths, exclusions and frozen criteria in the brief; records the dispatch lineage itself; and closes the record of a child that crashed, timed out or aborted without a closing note.
- No coordination layer? Then this section is one line of the brief: "say what you own before you start, and tell me when you stop." Nothing else changes.
3c. Reviewer child: a fresh, independent read-only session
After an implementer hands back, and before you accept or merge, dispatch a fresh session to review the change. Fresh context is the point: the implementer's history, and yours, are polluted by the path taken, while a new session sees only the diff, the brief and the evidence. Run one per lane and again after each correction; it is not only a last wave.
- Route (a role, not a model): one of your recommended worker models, on a different model family from the implementer, at the highest thinking level its live route advertises, never a metered provider without direct authorisation, respecting disabled or suspended backends — rules in
references/routing.md(Use an independent reviewer). Check provider headroom first and confirmfallbackApplied:falseon create. - Create with
cwd= the implementer's worktree and durable retention (ttlSecondssized for the wait,ownerId:"orch-<prog>-<parentShort8>-<lane>-reviewer"), dispatch detached, watchagent_endwithmax_wakesheadroom (each auto-compaction also fires anagent_end). - Read-only is enforced by the brief only. Check
git statusis clean afterwards. - The verdict is evidence, not sign-off. You still re-run gates and adjudicate; a defect goes back to the same implementer as a bounded correction, and the same reviewer closes the previous findings.
Proportionality. A finding must be able to change the decision; if it cannot, verify, record the limitation and accept. Use a capability-strong reviewer for safety-critical code (cheap reviewers have missed a HIGH confirm-gate gap that a stronger reviewer caught). A quick "ACCEPT, no findings" from a cheap reviewer is a weak signal, not sign-off. Time-box reviews. Never shortcut production safety checks.
Cap: one full review, one closure round on the same reviewer, then a final parent-verified correction (no round 3). Brief contents, the verdict format, the closure loop and the compaction gotchas are in references/multi-phase.md (Review wave). For several parallel critiques of one draft, use references/patterns.md (Reviewer fan-out) instead.
4. Set a long task and walk away
Four pieces combine into "dispatch and check back later": retention keeps the session alive, detached dispatch starts the turn and returns, watch-wake prompts you when the child finishes, and notifications ping the human.
Who are you? Decide this BEFORE any detached dispatch. Only a managed session
(one Pi Web UI created) can be an onFire target. A bare CLI process cannot be
prompted by the server: a server-side onFire armed for it — by you, a script, curl
or a repo dispatcher helper — sits undelivered while you idle. Observed: a parent audited
its script-armed watch as active, asserted it would auto-resume, and slept until a human
noticed. A server-side watch existing is not a wake being deliverable. Before ending
your turn, name the exact tool call that delivers each wake into this session.
| You are | Wake delivery (plus one model-free backstop) | Playbook |
| Managed session, any runtime | server onFire on the child targeting your $PI_WEB_UI_SESSION_ID, plus a deadline condition | references/walk-away.md |
| Bare Pi CLI | watch_wake_register from this session + wake_deadline; if the tool is unavailable/broken, an owned pure-observer ledger plus a bounded in-harness relay whose process exit wakes you (record its actual delivery id; a raw server watch never counts as local delivery) | long-horizon-waiting-strategies |
| Bare Claude Code CLI | the watch-wake mod: mcp__watch-wake__watch_wake_register + wake_deadline (wakes a fully idle CLI); an exiting background watcher only as fallback | references/claude-code-orchestrator.md |
Antigravity agy / IDE | scripts/wait-watch.sh <watchId> via run_command + native schedule backstop | references/antigravity-orchestrator.md |
| Command Code | no published watch-wake port: park inside a capped sleep (it cannot start an idle run) | references/walk-away.md (Bare-CLI parents) |
claude -p / one-shot | cannot wait: hand back instead | — |
The rules worth carrying in your head:
- Watch the child, wake the parent. Register the watch on the child with an
onFireaction targeting your own session, then return your turn. Never watch yourself — an idle session emits no events. - Command-shaped work is not child work. Long shell jobs (test suites,
builds, watchers, validation scripts) run natively where the harness provides a
background command (a background-shell extension, or the harness's own
run_in_backgroundmechanism): launch, end your turn, and the completion wake carries the exit code and output tail — no child session, no watch, no tokens while waiting. That is for you, the parent: a child that ends its turn on its own background job wakes you with a false hand-back, which is why children run long commands in the foreground (agent-os-child§2). Reserve child dispatch and watch-wake for work that needs another model context; cross-runtime waiting rules are unchanged. - Bare-CLI parent ⇒ local delivery is mandatory (table above), then one
appropriate backstop per
long-horizon-waiting-strategiesbefore idling: the loadedwake_deadlineon a healthy host, an independent fallback when the host/tool itself is suspect. Repo scripts that arm their own server-side watches serve audit/durability — they do not count toward your wake. event_type: agent_endis the default condition. One wake per child turn, and it also fires when the child stops early to ask, which is exactly when you should look.- Parallel children are supported. Wake budgets are per watch record, so N children give N independent budgets with no cross-child suppression.
- Two fan-in rules (contract 1.32.0+): one watch per session
(re-registering explicitly returns
replaced:trueand starts a new ledger), and only one steer may be pending per target. Usemode:'steer'for a busy Pi/Claude SDK parent and reconcile every child on any wake; additional concurrent steers are recorded assteer_pendingsuppression. Transient delivery failures do not consume budget and get one bounded retry. - Never poll in-turn. Holding your turn open to watch a child is the failure
this whole section exists to remove. That includes a foreground
pi-orch wait: it is for scripts and in-turn waits; an idle parent registers a watch and ends its turn (references/pi-orch.md, Waiting). 202is queue acceptance, not delivery. Never report "notified" from it.
→ references/walk-away.md has the full detail: retention modes and lease
ownership, the watch-wake payload and its cross-runtime matrix, the fan-in
best-practice list, bare-CLI wake paths, every condition type with what it
actually promises, and the notification edges. Read it before your first
detached dispatch.
4b. Goal-driven children (contract 1.27.0)
A goal is a durable objective that survives compaction on every supported runtime —
the strongest primitive for long-horizon children, and protection against ambient traffic
that sounds like a session ending (a plain-prompt probe child once stopped mid-task to
obey an automated end-of-session summary request). Arm one for work that must outlive a context window or
must not be derailed; use a plain prompt for bounded single-turn tasks. Write the aim,
not the method: what must be true, the evidence that settles it, where the record lives,
the invariants. Arm via POST|GET /api/v1/sessions/:id/goal or atomically at create; watch
terminal goal_end (filter a reused session's old-goal clear by the exact new objective),
not per-run agent_end. A start receipt is not completion. The objective must be a
single line (the API rejects newlines), and a goal that may run long needs an explicit
token budget (budgetTokens; pi-orch --goal-budget-tokens): the
5,000,000 default pauses it mid-run — measured programme lanes needed tens of millions.
→ references/goals.md — the API, per-runtime honesty matrix, read-back discipline.
Read it before arming a goal.
4c. Pausing YOUR OWN goal to supervise a child (Pi coding agent only)
Keep the parent goal running while independent work remains. Before idling: arm the watch
and backstop, goal pause (reason "supervising <child>"), end the turn. Never end
with Status: NEEDS_USER_INPUT for supervision (the wake would auto-resume the goal), and
resume only when fully settled (no re-dispatch, questions or child watches outstanding).
Verify the goal engine version you rely on is actually loaded.
→ references/goals.md (Part 2) — full lifecycle and trigger table. Read it before
dispatching a child under an active goal.
5. When a child asks a question
A child that stops to ask is otherwise a dead end. Read pending requests with
GET /sessions/:id/approvals/pending and answer via
POST /sessions/:id/approvals/:requestId/respond — structured answers keyed by
the exact question text, cancelled: true to dismiss. Never fabricate an
answer on the operator's behalf, and never treat an accepted HTTP response as
resolved work.
→ references/sessions.md has the exact request/response shapes, error
codes, and the permission-request form. Open it before answering a child.
6. Monitor, and know what counts as evidence
| Need | Endpoint |
| Live progress | GET /sessions/:id/events (SSE) |
| Completion signal | GET /sessions/:id/wait?status=idle&timeout=… |
| Durable dispatch state | GET /runs/:runId |
| One bounded troubleshooting read | GET /sessions/:id/evidence |
| Scoped logs, no shell needed | GET /sessions/:id/diagnostics, GET /diagnostics |
/events is supervision, not durable completion evidence. For detached or
disconnect-prone work, the receipt is the truth.
- A
/waittimeout means "still running", not "failed". Re-wait; never restart, re-dispatch or alert on one. - Before reporting a blocker, rule out your own harness — over-strict route assertions, parsers that reject valid JSON wrapped in prose, schemas stricter than the one you asked for. A false blocker costs the operator more than a slow run.
- A blank transcript projection is not proof there was no answer. Compare the full projection, receipt evidence, history and diagnostics first.
- Terminal codes that are not child failures to retry as-is.
NEVER_STARTED(1.57.0): an accepted, dispatched run showed no runtime activity inside the start window (default 120 s). The watch fires withrunNeverStarted: true. The dispatch never ran: re-dispatch once, then look at the session. Turns started by an extension or the browser (goal continuations, watch-wake deadlines) have no receipt, so this does not cover them; keepgoal_endwatches plus a backstop.RUN_TRANSPORT_LOST(1.57.0): a synchronous dispatch's receipt went terminal but its response chain never returned. The outcome is in the receipt; read it and do not re-dispatch blindly.RUN_BUDGET_EXCEEDED: the run hit a per-run cap. The caps are streamed tool arguments (64 KiB per call, 256 KiB per run), 1,000,000 output tokens, and 4 MiB of streamed output. Therun_budget_exceeded/tool_args_budget_exceededevent names which cap. Split the task, or ask for files instead of inline output.interruptedwithSERVER_RESTART: a deploy cut the run off. The watch fires withinterruptedByRestart: true, and receipt-less goal or extension turns carryrunId: "busy-<sessionId>"(1.52.0). On contract ≥ 1.60.0 a Pi goal child cut off by a RESTART interruption does not need you: the server continues it once at boot — agoal_statewithstatus: "running"+ top-levelautoContinued: true, progress, never agoal_end— and only a stop it could NOT continue surfaces asgoal_statepaused+pausedReason: "interrupted"with the cause — exactly one of:restart_interruption/rehydrate_pausethat could not be continued,second_transient,continue_failed,unsupported_runtime(non-Pi goals, boot sweep only). A provider-aborted goal is NOT continued: it ends exactly as it did before 1.60.0 (a failedgoal_end) and needs you as it always did. Resume a visible stop (POST /goal {"action":"resume"}) or re-dispatch; every non-Pi goal still needs the old resume-or-re-dispatch move. On older servers, resume or re-dispatch after the restart, as before. Watch forms:dataMatch {"autoContinued": true}sees continues;dataMatch {"status":"paused","pausedReason":"interrupted"}sees stops (both plus your objective filter);pi-orch waitalready registers the continue condition, counts continues intoautoContinuesand settles a visible stop as exit 5 with the cause and a note.
Completion blocks (1.58.0). A child that ends its final answer with a
pi-completion/v1 block (see §3's brief list) gets it parsed server-side. The receipt
carries completion (or completionError with a typed code and field path) and
completionDelimiter. GET /sessions/:id carries latestCompletion, which also
covers goal children whose final turns have no receipt. The block is the child's
claim, not proof: run pi-orch verify (or check commits and files yourself) before
accepting it. A contradicted verdict is a correction item, not a retry.
→ references/evidence.md has the five-step evidence ladder, the
receipt.outputEvidence dispositions, transcript scope semantics (including the
scope=screen trap), what receipt terminality does and does not prove, and the
Claude channel /events caveat. Read it before declaring anything complete.
7. Find and identify sessions
GET $API_BASE/sessions # list: sessionId, runtime, model, status, cwd, messageCount, lastActivity
GET $API_BASE/sessions/:id # detail
GET $API_BASE/sessions/:id/info # enriched runtime/session metadataListing is the natural first move when you arrive cold and need to know what is
already running. The list returns the full persistent registry — including
long-dead sessions — ordered newest-first by lastActivity; liveness comes
from busy/lastActivity, never from mere presence.
→ references/sessions.md has server-side filters (contract 1.30.0), the
native-CLI session scan, and the identifier-forms warning (internal id vs
sessionPath vs runtime-native id — resolve first, never guess).
8. Adopt existing sessions as children (contract 1.40.0)
Adoption links an already-existing session under you after the fact, with the same
linkage and fan-out as create-time children. A registered session: POST /sessions/:id/adopt
(or control action adopt). A native CLI session never registered: discover with
GET /sessions/native, then POST /sessions/adopt-native and use the returned internal
sessionId. Adoption is display-only metadata — it never moves transcripts or changes
ownership — and adopted children are full supervision targets.
Operator gate — never skip. If the session was initiated or used in a CLI tool (
claude,cmdc,agy,opencode,pi), ask the operator and wait for explicit confirmation that they have exited that CLI before adopting or dispatching. Your dispatches resume the same native artefact in the server's subprocess; a still-open CLI is a second live writer (Claude refuses--resumewithSession ID already in use, and concurrent writers diverge the transcript).
→ references/adoption.md — discovery recipes, payloads, responses, errors,
ownership/fencing, post-adoption wiring, worked example. Read it before your first adoption.
9. Transfer, aggregate, clean up
POST /sessions/:id/transfermoves visible context into a target session you own — cleaner than inventing your own handoff framing. It is a handoff, not shared memory; track the target id and send a follow-up instruction after.POST /sessions/usageaggregates token/cost across the batch. Keep the session/run mapping so cost stays attributable.- After positive quiescence,
release_retentiononly the exact lease you own (payload keyretentionLeaseId). ThenPOST /sessions/:id/abortif work must stop, andDELETE /sessions/:idfor disposable sessions you created. DELETEof a Pi session runs its extensions'session_shutdown(reasonquit, bounded 5 s): the child's background-shell processes (dev servers, watchers) and goal/watch timers end with it, and the call can take up to 5 s longer. Collect anything a child's background task produced before deleting it.- If release or deletion is unconfirmed, preserve that uncertainty in the handoff. Never blanket-unpin, broad-kill processes, or delete an operator-owned session.
10. Orchestrator governance
For the parent only (children follow agent-os-child). The full playbook, with the
decision boundaries, restart protocol and notification standards, is
references/orchestrator-governance.md; the rules to carry:
- Decide and fix autonomously within scope. Diagnose defects, design fixes, write briefs and dispatch small-to-medium corrections without asking; keep the operator informed at milestones. Escalate only what the governance file lists (destructive or irreversible steps, external submissions, production restarts and deploys unless the programme's authority already covers them, locked-decision pivots, quota exhaustion, unadjudicable ambiguity).
- One writer per working tree. Concurrent children in one repository each get an isolated git worktree with disjoint owned paths; where a worktree is impractical, queue the second child until the first has committed. Cross-repository parallelism is safe.
- Sequence gating is yours, not the operator's. When an earlier lane lands and the tree is clear, un-gate the queued lane at once; never ask "may I start phase X?".
- Production restarts of the service that hosts your orchestration: only with the
operator's authority for the programme, only by the parent (never from inside a child it
hosts), and through the deployment's drain-then-restart procedure. Pi Web UI ships
production:drain-restartunder a production lock; adapt it to your deployment.- On contract ≥ 1.52.0 the drain waits for every busy session, goal continuations and browser turns included.
- On contract ≥ 1.60.0 a Pi goal child the restart still cuts off continues once on its
own at boot; you resume only the stops it reports as
paused/interrupted. - On an older server, pause goal-armed children first: its drain cannot see their turns.
- Afterwards verify
/capabilitiesand reconcile every child and watch (references/orchestrator-governance.md§3).
- On every wake, check every child, not only the one named in the wake.
- Flatten child chains: adopt or re-parent finished sub-workers under your own record.
- Mandate
agent-os-childin every dispatch (§3) and require child-side task announcement with owned paths. - Verify every fix a child claims against the diff, not its report. Children have claimed fixes they never made and root causes that were wrong; a correction brief should demand a file:line list per claimed fix and a test on the real code path, and the parent checks both (details in the governance file §8).
- Contain your own proofs. Any memory or isolation experiment runs under its own hard memory cap, inside a subtree you created and verified, with self-capped allocations: an escaped proof once grew without bound and the host OOM killer took the service down.
- Run the CI's gate list, not a subset, before merging or deploying — including
changed-source ratchets against an explicit base (
npm run lint:ratchet -- --base <lane base or previous master>in a repo that has one; the default base sees nothing on a merge commit). Locallintpassing while CI's ratchet fails is a real gap. - Notify at milestones through your own notification path — a short milestone, question, blocked or done message, never as a running commentary — and check that it was delivered.
Current limitations
- Parent linkage and child surfacing exist from contract 1.34.0 (automatic
lineage and
?parent=since 1.54.0), but there is no general orchestration job registry. Keep the programme checkpoint and exact session/run/watch/lease mapping; linkage is not execution or completion proof. - Run receipts are dispatch-scoped; there is no general job queue or scheduler.
/approvals/pendingis backend-specific; runtimes without a synchronous pending surface return an empty list.- Command Code exposes only its attested shadow profile through the Internal API. Browser-contained Command Code sessions are deliberately invisible to Internal API session, diagnostic, notification, receipt, and transfer routes.
Related
- Pi Web UI live-validation documentation — proving a Pi Web UI or runtime change works, short-horizon or long-horizon, always on a disposable server. Use it instead of this skill whenever the question is "does my change work?".
long-horizon-waiting-strategies— going idle safely during long waits: the pre-idle checklist, harness-specific wake and backstop mechanics (Pi, Claude Code, Antigravity, Command Code), and the supervision and liveness rules.agent-os-child— the skill every child must follow; you mandate it (§3).secret-scanning— the credential-scanning companion for work that produces artefacts, evidence bundles or repositories.- In the Pi Web UI repo when present:
docs/INTERNAL-API-QUICKSTART.md,docs/INTERNAL-API.md,docs/INTERNAL-API-ORCHESTRATION.md,docs/INTERNAL-API-RECIPES.md,docs/INTERNAL-API-CONTRACT.md,docs/NOTIFICATIONS.md,docs/SHARP-EDGES.md,docs/DURABILITY-MATRIX.md,docs/TROUBLESHOOTING.md,docs/CLAUDE-BACKENDS.md.
If the repo is not checked out, this skill plus live /capabilities is enough.
Part of the Pi Web UI orchestration set
- Pi Web UI — https://github.com/valtterimelkko/pi-web-ui (docs:
docs/ORCHESTRATION-OVERVIEW.md,docs/INTERNAL-API-ORCHESTRATION.md). - pi-orch — https://github.com/valtterimelkko/pi-orch, the parent client whose bin is
pi-orchon${PATH}. - The pack — https://github.com/valtterimelkko/agent-workflow-skills/blob/main/packs/pi-web-ui-orchestration-pack/README.md.
- The watch-wake companion —
companions/watch-wake/in this repository: the Pi extension and the Claude Code mod that wake an idle parent, plus the model-free wake deadlines. - Sibling skills in this repository:
../agent-os-child/,../long-horizon-waiting-strategies/,../secret-scanning/.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

