Start and cancel pi-goal managed runs from an intercom message, with status replies sent back to the sender.
A session that receives an intercom message containing a GOAL:START marker
starts a real pi-goal goal (via the pi-goal:start Managed Run RPC on the
shared pi.events bus), then reports active / terminal outcome / rejection
back to the sender over intercom. No shell-out, no typing /goal, no second
human needed.
This fills a real gap: pi-goal exposes the managed-run RPC, and pi-intercom
provides inter-session transport, but nothing shipped wires the two together —
an agent cannot start a goal for itself or for a peer, because /goal is a
human-typed host command and the agent-facing goal tools
(goal_complete/goal_blocked/goal_wait) all require an already active
goal.
# from GitHub (tracks the default branch)
pi install git:github.com/fankserver/pi-goal-intercom
# pinned to a tag or commit
pi install git:github.com/fankserver/pi-goal-intercom@v0.1.0
# local checkout
pi install /absolute/path/to/pi-goal-intercomRestart the target Pi session (or /reload) so the extension loads. dist/ is
committed, so a git install needs no build.
Any intercom message body that reaches the target session. A command must be the first non-empty line of the message body, unwrapped in code delimiters — a marker quoted mid-sentence, in backticks, in a blockquote, or in a fence is prose and is ignored:
GOAL:START <objective> [--tokens <N>]
GOAL:CANCEL [reason]
GOAL:STATUS
From a sender session:
intercom({
action: "send",
to: "worker",
message: "GOAL:START finish README and commit it --tokens 800000",
});Examples:
GOAL:START debug the flaky test in ./internal/policy until it is green --tokens 2000000
GOAL:CANCEL owner changed direction
GOAL:STATUS
The sender gets status replies back over intercom, each reporting what pi-goal actually did rather than what was requested:
▶️ GOAL active: finish README and commit it
✅ GOAL complete: finish README and commit it — <completion summary>
⛔ GOAL blocked: … — <reason> (also usage/paused/cleared)
💰 GOAL budget_limited: … — token budget reached (1.2M/1M) Recovery: ask a human to run `/goal edit --tokens <higher>`…
🛑 GOAL cancelled: paused — owner changed direction
⚠️ GOAL:CANCEL refused (RUN_NOT_FOUND): … A human must run `/goal clear`…
⚠️ GOAL:START rejected (RPC_DISABLED): Managed run RPC is disabled.
ℹ️ GOAL:STATUS — run pgi-1a2b… / last status: budget_limited / STUCK…
A resume or /reload re-presents the entire message history to the extension, so
a command that already ran arrives again looking brand new. Commands are therefore
exactly-once per delivery: the envelope delivery stamp must not predate this
process, and the delivery id is recorded durably in
~/.pi/agent/pi-goal-intercom-processed.json (PI_GOAL_INTERCOM_STATE_DIR
overrides). Restarting a session therefore never silently re-runs its last goal.
Dedupe is per delivery, not per text: a deliberate retry re-sending the same
objective is a new delivery and does act again, which is what makes a retry
predictable.
The budget is cumulative across the whole goal run, and every model request
re-sends the full conversation with cached prompt input billed. A real turn
cost input 39,837 + cacheRead 20,096 + output 93 = 60,026 tokens — 60k billed
for 93 tokens of work. Therefore:
cost ≈ turns × contextTokens → --tokens ≥ max(150000, planned_turns × contextTokens × 1.3)
Leave it out unless you have a specific reason to cap spend. --tokens is
honoured when you pass it — dropping a ceiling you asked for would run past what you
authorized — but it is not part of the recommended form, because its failure mode is
state damage rather than a clean stop:
- The budget is cumulative cost, not work, so it cannot be sized from the objective alone.
- Exhausting it leaves a stopped goal holding the slot, which managed cancel cannot
free; a human must run
/goal edit --tokensinside that session. - Below roughly one turn it is strictly negative: one request burned, no work, slot jammed. Observed at 40k and 60k against warmed 140k–235k sessions.
So the extension enforces a floor of 150,000 in code rather than trusting
documentation — an earlier version of this skill recommended sizing budgets, several
coordinators obliged, and four sessions jammed. Anything below the floor is refused
with BUDGET_TOO_LOW, and a malformed value (--tokens abc, --tokens 40k) is
refused too rather than quietly treated as "no budget", which would run unbounded
after you asked for a cap.
Read contextTokens from intercom list and size for the whole horizon.
automaticTurns is unlimited by design; runaway loops are caught by noProgressTurns
plus repeated-output fingerprinting, not by a turn cap. For long goals the dominant
lever is context size.
pi-goal's managed cancel only pauses a goal that is exactly active. A goal in
budget_limited, paused, blocked, or usage_limited persists and keeps
rejecting starts, and clearing it is not reachable over the event bus (only
pi-goal:start and pi-goal:cancel exist). Both need a human in that session:
- Budget exhausted on real work →
/goal edit --tokens <higher>, which raises the ceiling and resumes the same goal with objective, cumulative usage, and elapsed time intact. Not/goal clear— that discards the objective and every accumulated turn. - Goal genuinely wrong →
/goal clear, which is still cheaper than restarting the session (a restart also discards the session's context).
Send GOAL:STATUS to detect this instead of guessing from GOAL_ALREADY_EXISTS.
The package ships a skill, pi-goal-intercom, declaring the protocol to agents.
Without it the capability is invisible — README/docs are never read by a model,
so a session would not know GOAL:START exists.
Pi advertises the skill's name + description at startup and loads the full instructions when a task matches (delegating autonomous work to a peer session, "set a goal" for another session, reporting a delegated goal's outcome). Force it with:
/skill:pi-goal-intercom
It documents both sides: sender-side GOAL:START / GOAL:CANCEL / GOAL:STATUS
syntax, the strict command-position rule, budget sizing for long-horizon runs, the
status replies to expect, a failure table (RPC_DISABLED, GOAL_ALREADY_EXISTS,
silent no-op), stuck-slot recovery, the session-name scope gate, and the things
that cannot work (no goal-start tool; /goal in a message body is inert).
Restart the session after installing so the skill is discovered.
| Requirement | Why | If missing |
|---|---|---|
@narumitw/pi-goal (or any pi-goal implementing Managed Run RPC v1) |
provides pi-goal:start / pi-goal:cancel |
start is logged and rejected back to the sender |
pi-goal.json → "rpc": { "enabled": true } |
the Managed Run RPC is opt-in for security | starts return RPC_DISABLED |
| pi-intercom present in the session | status replies use intercom:outbox-request |
goal still starts; replies are skipped |
| A session the extension is allowed to act in | scoping, see Safety | fully inert |
The extension is inert unless explicitly allowed:
- the session's display name starts with
test(test-1,test-2, …), or PI_GOAL_INTERCOM_ALLOW=1is set in the target session's environment.
Everywhere else the context hook returns immediately, so installing it
globally is safe: a stray GOAL:START reaching a non-test session is ignored.
A started goal runs paid autonomous turns, and automaticTurns: null is the
intended configuration for long-horizon goals — there is no response-count cap,
because legitimate goals may run for weeks. The guards are therefore the
cumulative --tokens budget (see Sizing --tokens) and pi-goal's no-progress
detection. Treat GOAL:START as a real instruction and always pass a budget sized
to the whole run. Status replies are feedback, not authorization.
npm install
npm run typecheck # tsc --noEmit
npm run build # verify + stage dist/index.ts (committed)
npm run check # build + smoke testSkills are declared explicitly under pi.skills in package.json (an explicit
pi manifest disables conventional directory discovery) and live in
skills/<name>/SKILL.md with Agent Skills spec frontmatter.
After editing src/, always npm run build and commit the regenerated
dist/index.ts — it is what Pi loads. See
docs/DEVELOPMENT.md for the event-bus protocol, message
shape details, and design notes.
MIT