agentleFS
valtterimelkkovaltterimelkko/agent-workflow-skills/skills/pi-web-ui-internal-api-orchestration/SKILL.md

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.

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
  1. Distinctive:

    Contract version: Every feature note below names the Internal API contract version that introduced it.

    line 82
  2. Very specific:

    Fallback: raw requests: Use them for anything the client does not cover (adoption, transfers, diagnostics), or when pi-orch is unavailable.

    line 119
  3. Very specific:

    When you run inside a Pi Web UI-managed session, PI_WEB_UI_SESSION_ID (contract 1.47.0.

    line 141
  4. Distinctive:

    retained and disabled: Do not try to route through it.

    line 177
  5. Distinctive:

    Ask the server what exists right now.

    line 181
  6. Distinctive:

    Read actual tasks, reservations and the limiting reason—not just the nominal turn ceiling.

    line 212
  7. Very specific:

    Never use a remembered model list, a family-name match, a coarse reasoning flag, a stale contract version, or a guessed fallback.

    line 266
  8. Very specific:

    If the Internal API persistently refuses admission (e.g.

    line 284
  9. Distinctive:

    Treat a dispatch route as the complete tuple:

    line 290
  10. Distinctive:

    Prefer a single create when atomic retention matters — batch creation does not guarantee leases.

    line 384
  11. Very specific:

    The create response's model echo is not proof of binding: Verify the effective, provider-qualified model via /info (and for Pi, the last …

    line 406
  12. Very specific:

    Every successful response and receipt carries dispatchMode, the operation *actually* performed, alongside the requested mode.

    line 454
  13. Very specific:

    Put the directive in the prompt itself, not only in a brief file it points to, and restate it inside a goal objective ("follow …

    line 492
  14. Distinctive:

    The RED method: test first; no git stash (its stack is shared across worktrees).

    line 503
  15. Distinctive:

    Restarts stay with you: never delegate a restart of the service that hosts the child.

    line 504
  16. Distinctive:

    After an implementer hands back, and before you accept or merge, dispatch a fresh session to review the change.

    line 555
  17. Distinctive:

    Read-only is enforced by the brief only: Check git status is clean afterwards.

    line 562
  18. Very specific:

    Who are you? Decide this BEFORE any detached dispatch: Only a *managed* session (one Pi Web UI created) can be an onFire target.

    line 575
  19. Very specific:

    202 is queue acceptance, not delivery: Never report "notified" from it.

    line 626
  20. Very specific:

    Keep the parent goal running while independent work remains.

    line 654
  21. Distinctive:

    Verify the goal engine version you rely on is actually loaded.

    line 658
  22. Very specific:

    After positive quiescence, release_retention only the exact lease you own (payload key retentionLeaseId).

    line 784
  23. Distinctive:

    If release or deletion is unconfirmed, preserve that uncertainty in the handoff.

    line 792
  24. Distinctive:

    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.

    line 811
  25. Distinctive:

    Run receipts are dispatch-scoped.

    line 847
  26. Very specific:

    If the repo is not checked out, this skill plus live /capabilities is enough.

    line 871

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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…SkillDefault target
Dispatching real agent work — fan-out, comparison, a long task to check back onthis skillthe production socket
Proving a change to Pi Web UI or a runtime actually worksthe repository's live-validation documentationa 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.

ReferenceOpen it when
references/pi-orch.mdbefore your first pi-orch call (verbs, flags, exit codes)
references/walk-away.mdbefore your first detached dispatch (conditions, fan-in)
references/claude-code-orchestrator.md / antigravity-orchestrator.mdyou orchestrate from that harness
references/goals.mdarming a goal, or dispatching under your own goal
references/evidence.mdbefore declaring a child complete
references/sessions.mdbinding a model, answering a child's question, identifying sessions
references/adoption.mdadopting a session as a child
references/orchestrator-governance.mdseveral children, restarts, defects
references/multi-phase.md, programme-kit.mda multi-wave programme (templates: assets/programme-kit/)
references/patterns.mddesigning a fan-out or reporting to the operator

Where things are in this file:

If you are…Read
Making your first callsConnect from anywhere, Golden rule: discover first
Picking runtimes/models/roles for childrenChoosing 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 — verify contract.name is pi-web-ui-internal-api and check contract.majorVersion. Capability-gate optional features rather than assuming them. The runtime feature flags are per-runtime, nested under runtimes.<runtime> (not top-level): read supportsFollowUp, followUpSemantics, supportsSteer, supportsSteerWhileBusy, supportsThinkingLevel, supportsApprovals, supportsInteractiveQuestions, supportsReplayHistory from 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 return 429 ADMISSION_CAPACITY_EXHAUSTED for slot/runtime limits, or 503 for resource pressure, with Retry-After. Resource refusals include heap_pressure and event_loop_lag (1.49.0; /capacity shows heap and eventLoopLag), and 503 SERVER_DRAINING while a deploy drains (1.51.0). Treat any 429/503 with Retry-After as "wait and retry" (a drain ends in a restart: re-check /capabilities afterwards), and never read the wire contract's code list as closed. An event_loop_lag refusal usually means the host is overloaded (another agent's build or benchmark), not that your request is wrong: pi-orch waits out Retry-After for you; raw callers must too. Heavy work of your own belongs under nice and capped CPU and memory (systemd-run --scope -p CPUQuota=… -p MemoryMax=… -- nice -n 10 <cmd>; some systemd-run versions reject -p Nice=10, so put nice inside 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 its id, provider, thinkingLevels, and for Claude profiles backend and claudeModel. A missing field is unknown, not permission to fill it in from memory. Profile ids are opaque strings; since contract 1.25.0 both /models profile entries and /capabilities.claudeProfiles carry the same underlying alias in claudeModel / model. After creating a session with an explicit model, trust only the create response's resolvedModel / modelBinding fields (contract ≥ 1.25.0) or a follow-up /info read-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. See walk-away.md.
  • Provider quota — /capacity is Pi Web UI's local admission capacity and /sessions/usage is 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.

PathWho paysWakeCoordination record
Internal APIsubscription pools; metered only with operator leaveserver watchvisible in Pi Web UI
Agent tool subagentthe harness's own plantask result in your turnnone
Claude Code cloud childseparate cloud creditsnone 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 to cd into 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 capped git ls-remote watcher as a background task in the dispatch turn (Bash timeout 7,200,000 ms; the 30 min default kills it) and arm a ≤ 60 min wake_deadline that checks get_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 role

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

FieldNotes
messagerequired
verbosityanswers (default, non-streaming, final text only), tasks (SSE status headlines), full (SSE everything)
modeprompt, follow_up, or steer — see below
detachfire-and-forget; only valid with verbosity=answers, otherwise 400
idempotencyKeysession-scoped, 1–128 chars. A matching retry reuses the run; a different payload on a live key returns IDEMPOTENCY_KEY_CONFLICT
requireActiveTurnfor follow_up, forbid idle promotion

Modes — this is how you redirect a child that's already working, and it's easy to miss:

ModeMeaningPiClaudeOpenCode / Antigravity
promptstart a new turn409 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 dispatch409 SESSION_BUSY if running409 SESSION_BUSY if running
follow_updeliver after the current turnbusy 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 settlementno queue: running → 409 SESSION_BUSY + Retry-After; idle → new turnOpenCode: 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
steerjoin the active turnrequires an active turn; idle → 409 SESSION_NOT_STREAMINGSDK 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_OPERATIONOpenCode → 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/:runId

Large 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-child skill. 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-orch appends it for you (for a goal child also the Status: 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.json entries that carry apiKey as 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 a systemd-run --scope with a hard MemoryMax; changed-file lint against the lane base; never touch the main checkout. The child-side form is agent-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 sessionId from POST /sessions so 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 confirm fallbackApplied:false on create.
  • Create with cwd = the implementer's worktree and durable retention (ttlSeconds sized for the wait, ownerId:"orch-<prog>-<parentShort8>-<lane>-reviewer"), dispatch detached, watch agent_end with max_wakes headroom (each auto-compaction also fires an agent_end).
  • Read-only is enforced by the brief only. Check git status is 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 areWake delivery (plus one model-free backstop)Playbook
Managed session, any runtimeserver onFire on the child targeting your $PI_WEB_UI_SESSION_ID, plus a deadline conditionreferences/walk-away.md
Bare Pi CLIwatch_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 CLIthe watch-wake mod: mcp__watch-wake__watch_wake_register + wake_deadline (wakes a fully idle CLI); an exiting background watcher only as fallbackreferences/claude-code-orchestrator.md
Antigravity agy / IDEscripts/wait-watch.sh <watchId> via run_command + native schedule backstopreferences/antigravity-orchestrator.md
Command Codeno 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-shotcannot 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 onFire action 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_background mechanism): 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-strategies before idling: the loaded wake_deadline on 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_end is 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:true and starts a new ledger), and only one steer may be pending per target. Use mode:'steer' for a busy Pi/Claude SDK parent and reconcile every child on any wake; additional concurrent steers are recorded as steer_pending suppression. 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).
  • 202 is 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

NeedEndpoint
Live progressGET /sessions/:id/events (SSE)
Completion signalGET /sessions/:id/wait?status=idle&timeout=…
Durable dispatch stateGET /runs/:runId
One bounded troubleshooting readGET /sessions/:id/evidence
Scoped logs, no shell neededGET /sessions/:id/diagnostics, GET /diagnostics

/events is supervision, not durable completion evidence. For detached or disconnect-prone work, the receipt is the truth.

  • A /wait timeout 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 with runNeverStarted: 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; keep goal_end watches 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. The run_budget_exceeded / tool_args_budget_exceeded event names which cap. Split the task, or ask for files instead of inline output.
    • interrupted with SERVER_RESTART: a deploy cut the run off. The watch fires with interruptedByRestart: true, and receipt-less goal or extension turns carry runId: "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 — a goal_state with status: "running" + top-level autoContinued: true, progress, never a goal_end — and only a stop it could NOT continue surfaces as goal_state paused + pausedReason: "interrupted" with the cause — exactly one of: restart_interruption/rehydrate_pause that 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 failed goal_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 wait already registers the continue condition, counts continues into autoContinues and 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 metadata

Listing 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 --resume with Session 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/transfer moves 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/usage aggregates token/cost across the batch. Keep the session/run mapping so cost stays attributable.
  • After positive quiescence, release_retention only the exact lease you own (payload key retentionLeaseId). Then POST /sessions/:id/abort if work must stop, and DELETE /sessions/:id for disposable sessions you created.
  • DELETE of a Pi session runs its extensions' session_shutdown (reason quit, 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-restart under 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 /capabilities and 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-child in 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). Local lint passing 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/pending is 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

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.