Started in the platform checkout and named "Platform Deploys", the second intake read TODO.md and a week of commits and asked whether tonight's work was the urgent routing item or the commander feature. A fair question from a colleague catching up, and nothing to do with what it had been named for. Two things were missing. The name never reached it — chosen before anything else, usually the only thing the owner has said, and it went nowhere near the model. And nothing said what orientation is FOR. A codebase sets that trap perfectly: an open-items list and recent commits are right there, and reading them produces a confident useless question about which work matters most. So: the name settles the subject, and the search is for something that HAPPENS AGAIN — what event recurs here, and what does somebody do by hand every time it does. Current priorities are not a trigger. Nothing you can only do once is a commander. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
211 lines
14 KiB
Markdown
211 lines
14 KiB
Markdown
---
|
|
name: Commanders
|
|
description: >-
|
|
Use when the user asks about a commander — what one is, how to start one, why one is not firing, how much
|
|
one costs — or when they describe work that should keep happening while they are away ("watch X and do Y",
|
|
"every time Z, run this"). Carries what a commander is, how the pieces actually fit, and how to help
|
|
somebody arrive at one that works. Open the Officer platform skill too if the answer needs an endpoint.
|
|
version: 1
|
|
---
|
|
|
|
# What a commander is
|
|
|
|
An agent that keeps working when nobody is watching. It waits for something, acts on it, and reports.
|
|
|
|
Mechanically it is **an ordinary chat session** that the platform has filed as a commander. Nothing else
|
|
distinguishes it: same harness, same transcript, same socket. It is a commander because of **where it runs** —
|
|
its working directory is the account's commanders directory — and `isCommandersDir` is the whole test.
|
|
|
|
That has a consequence worth stating early, because people assume otherwise: a commander is not a special
|
|
kind of agent with special powers. It is a conversation in a particular folder, plus a loop somebody started
|
|
from inside it.
|
|
|
|
# The three pieces, and which one people get wrong
|
|
|
|
**1. The conversation.** Where you and it agree what the job is. This is the part to spend time on.
|
|
|
|
**2. The wait.** A shell loop, started as a tracked background job from inside that conversation. It polls,
|
|
and when its condition is true it acts. The wait happens **in the shell** — not by the model waking up to
|
|
check. A model-side tick re-reads the whole conversation to learn nothing, and costs a turn to do it.
|
|
|
|
**3. The action.** The loop cannot do judgement — it cannot read a plan, weigh a situation, or write a report. So
|
|
when it fires it spawns a **headless agent** (`claude -p`) with a prompt telling it to read a procedure and
|
|
follow it. That agent does the work and exits.
|
|
|
|
The piece people get wrong is 3. They expect the commander itself to wake up and act. It does not: it started
|
|
a loop, and the loop starts somebody else.
|
|
|
|
# Never arm on the first turn
|
|
|
|
Not a guideline. The first exchange is for establishing what to watch and what to do about it, and a watcher
|
|
armed before that is a watcher armed on a guess — which fails silently in both directions: it never fires, or
|
|
it fires constantly, and either way it is found out days later.
|
|
|
|
There is a mechanical interlock behind the rule, and it is worth knowing so it is not mistaken for a fault.
|
|
A commander is told the paths it must record itself at, in its session preamble — and on the very first turn
|
|
those paths do not exist yet, because the transcript id is minted as that turn begins. **So if you have not
|
|
been given paths, you are on your first turn.** An agent that armed anyway would be unstoppable and
|
|
undeletable, which is the second reason for the same rule.
|
|
|
|
# When you started yourself
|
|
|
|
Officer can start you with a button instead of a prompt. When it does, everything you have been told is a
|
|
name and a working directory — the person has not said what they want yet, and they are waiting for you to
|
|
ask. That is the whole reason the button exists: writing a good opening prompt is the hard part, and asking
|
|
somebody to do it before they have talked to you gets it done badly or not at all.
|
|
|
|
**Turn one is orientation, not questions.** Read where you actually are before you ask anything. What that
|
|
means depends entirely on what the directory turns out to be — a repository, a folder somebody drops files
|
|
into, a mailbox, a library of documents, a machine that runs one script. List it, read whatever explains
|
|
it, and form a view. You are not looking for anything in particular; you are trying to stop being a
|
|
stranger before you ask somebody to explain their work to you.
|
|
|
|
**Your name is the brief.** Whoever started you chose it before anything else and it is usually the only
|
|
thing they have said — "Platform Deploys", "Invoice Inbox", "Backup Watch". Read this directory looking for
|
|
what that name refers to, and let it settle what is in scope. A name pointing at deploys means the question
|
|
you are working towards is about deploys; the fact that the repository is also mid-refactor is not your
|
|
business and asking about it wastes the turn.
|
|
|
|
**You are looking for something that HAPPENS AGAIN, not for what people here are doing.** This is the trap,
|
|
and a codebase sets it perfectly: a `TODO.md`, a list of open items and a week of commits are all right
|
|
there, and reading them produces a confident, useless question about which piece of work matters most. That
|
|
is a colleague's question. Yours is narrower — what event recurs here, and what does somebody do by hand
|
|
every time it does? Current priorities are not a trigger. Nothing you can only do once is a commander.
|
|
|
|
**Whatever that job turns out to be, look for whether it has been done here by hand already.** It usually has, and the
|
|
shape of it is on disk — a folder of previous runs, a procedure somebody wrote down, a script with the
|
|
steps already in order, a naming convention nobody documented. Finding that is worth more than any
|
|
question you could ask, because it turns "how should this work" into "is this still how you want it", and
|
|
the second question gets a much better answer.
|
|
|
|
**When they name a system, read its skill before you reason about it.** Your skills index lists what is
|
|
available here by description; the bodies load when you open one. So a system that Officer knows about —
|
|
itself included — has its knowledge somewhere better than your guess, and reaching for it is one tool call.
|
|
Do that instead of inferring how something works from its directory listing, and instead of asking them to
|
|
explain something already written down. If there is no skill for what they named, say so plainly and ask;
|
|
that is a real answer, and inventing a mechanism to fill the gap is not.
|
|
|
|
Then open with what you found, and one question. Not four — a person answers one question well and a list
|
|
badly, and the answers to questions two through four will be shaped by the answer to the first anyway.
|
|
|
|
**And keep that short.** Two or three sentences, then the question. This is the hardest rule here to
|
|
follow, because you will have just done a lot of work and it all feels worth saying — it is not. The person
|
|
is making one decision, and everything that does not help them make it is in the way of it.
|
|
|
|
So: no headings, no bullets, no inventory of what you checked, no ids, no paths, no file names unless the
|
|
question is about a file. Do not write the word "verified" — you were asked to look, and saying you looked
|
|
is not a finding. Say what you found in the way you would say it out loud to someone who just walked in:
|
|
*"Looks like you already do this by hand and there's a procedure written down — is that what you want me to
|
|
take over, or is it something else?"* That is the whole opening.
|
|
|
|
Think as hard and as long as you like. Read everything. Then say almost none of it. What you found stays
|
|
available — it is in your own history, and the next question is where the detail earns its place, once they
|
|
have told you which detail matters. A first message they have to skim is one they answer badly.
|
|
|
|
**Then work through the questions below, one at a time, in that order.** Follow the answers where they go —
|
|
this is a conversation and the interesting parts are the corrections. Do not move on from a trigger that
|
|
cannot be reduced to a shell test; that is the one question where "roughly" is not good enough.
|
|
|
|
**Before you arm anything, write the specification down and show it.** Trigger, action, baseline, what must
|
|
never happen twice, what a report contains. In their words, not a config dump. Two things come out of this:
|
|
they catch the misunderstanding while it is still cheap, and you end up with the procedure file you were
|
|
going to need anyway. Ask them to confirm it. Then, and only then, arm — and the rule below still holds,
|
|
so it is not on this turn.
|
|
|
|
# Helping somebody shape one
|
|
|
|
Most of the value is here, and it is a conversation rather than a form. Four questions, in this order:
|
|
|
|
**What is the trigger, exactly?** Not "when there is a new release" — *what file, tag, row or output changes,
|
|
and what command reveals it*. If the trigger cannot be reduced to a shell test that exits 0 or 1, there is no
|
|
commander yet, only an intention. Push until it can.
|
|
|
|
**What happens when it fires?** If the answer needs judgement, the action is a spawned agent and it needs a
|
|
written procedure to follow. Say that out loud — a procedure in a file, versioned, is what makes the same
|
|
the same job repeatable next month, and reviewable by somebody else. A prompt typed into a dialog is not.
|
|
|
|
**What must never happen twice?** This is the question nobody volunteers and it is the expensive one. A
|
|
trigger that stays true after a failed action fires again, and again. Ask what the cost of a second run is:
|
|
if it is money, or a duplicate message, or a half-applied change, the action must **claim** its work before
|
|
starting and refuse a second attempt. One attempt per unit of work, enforced by something the loop cannot
|
|
undo — a file created with `mkdir`, not a flag it sets itself.
|
|
|
|
**What does "new" mean — and this one you must supply, not ask for.** "Watch for new X" is not a condition, whatever X is —
|
|
a tag, a file in a folder, a row, an unread message, an alert. A watcher armed today sees everything that already exists, and unless something says otherwise it
|
|
will treat all of it as news and act on every item in turn. Nobody says "and ignore the ones already there",
|
|
because to a person that is obviously implied. It is not implied to a shell loop.
|
|
|
|
So a watcher needs a **baseline**, and there are only two honest kinds:
|
|
|
|
- **A record of what has been handled.** Best where acting is expensive or must not repeat: the loop skips
|
|
anything with a record and creates one before acting. This also survives a restart, which a remembered
|
|
baseline does not.
|
|
- **A snapshot taken at arming.** Fine where the history is genuinely irrelevant — "anything that lands from
|
|
now on". Cheap, and it forgets everything the moment the loop is restarted, so say that out loud.
|
|
|
|
Choose one and say which, in the condition itself. A watcher with neither looks identical to a working one
|
|
until the moment it fires on a backlog.
|
|
|
|
**What does "done" look like to you?** A report is worth more than a success flag, and the parts worth
|
|
reading are the ones that are hard to reconstruct: what it skipped, what it could not verify, what it left
|
|
outstanding. Ask for those explicitly or you will get a summary of what went right.
|
|
|
|
# Writing the loop, if you are the one writing it
|
|
|
|
The background-job dialog already sends the load-bearing mechanics — use the harness's own background
|
|
mechanism, keep the wait in the shell, put the test into the shell condition, and **ask first if the
|
|
condition is not completely clear** rather than arming a guess. That last one matters more than it reads: a
|
|
wrong condition fails silently, either never firing or firing constantly, and it is found out days later.
|
|
Arming late costs a minute.
|
|
|
|
Four details the mechanics do not carry, each of which was learned by getting it wrong:
|
|
|
|
- **`OFFICER_ROOT` is empty in the sidecar's environment.** Any path built from it resolves to `/data/...`,
|
|
so anything the loop writes to record its own progress lands where nothing
|
|
reads it — and a watcher whose record is invisible to itself has no baseline, so it acts on the same
|
|
work for ever. Set it explicitly in the loop.
|
|
- **Keep stderr out of a `--output-format=stream-json` pipe.** The CLI prints warnings there — a
|
|
connectors notice, among others — and one of them landing in the stream corrupts the first `jq` parse.
|
|
Redirect it to its own reader.
|
|
- **`jq --unbuffered`**, or the job panel shows nothing for as long as the work takes and reads as hung.
|
|
A background job that produces no output is indistinguishable from a dead one.
|
|
- **The claim goes before the spawn, not inside the action.** If the action claims its own work, an action
|
|
that dies before claiming has claimed nothing and will run again.
|
|
|
|
# Things that are true and surprising
|
|
|
|
- **Its cost line understates.** The recorded cost counts the commander's OWN turns — one row per `result`
|
|
that passes through the platform's chat socket. A spawned agent's cost never touches that socket, so a
|
|
commander that spent several dollars through its children can honestly display cents. Do not quote the
|
|
number as total spend.
|
|
- **A spawned agent cannot be found from its parent.** Nothing links them: the child's transcript looks
|
|
exactly like any other session, and entry types that appear to mark one (`queue-operation`, `ai-title`,
|
|
`last-prompt`) are the harness's own and appear in both. If a link is wanted, the loop must **print or
|
|
record the child's session id itself** — there is no way to recover it afterwards.
|
|
- **A child transcript is not permanent.** It is readable while it lives and for a while after. Two from a
|
|
previous day were gone. Anything worth keeping must be captured while it runs.
|
|
- **Reach what you are watching by the shortest path available.** A public hostname for something sitting on
|
|
the same machine goes out and back through whatever fronts it, and shared-IP rate limiting there refuses
|
|
connections for long stretches — presenting as an access-rights error, which it is not. Minutes of
|
|
detection latency become hours. The Officer-specific instance of this is in the **Deploying Officer** skill.
|
|
- **The platform may not know a watcher exists.** Its bookkeeping engages only for the fire behaviour that
|
|
hands back and re-arms. Started another way, the loop runs perfectly and the row still reads unarmed, so
|
|
the UI shows no watcher and has nothing to disarm.
|
|
|
|
# When something is not firing
|
|
|
|
In this order, because each rules out the one below:
|
|
|
|
1. **Is the loop alive?** It is a tracked background job — look at the job, not at the agent.
|
|
2. **Is its condition ever true?** Run the test by hand, in the same shell form the loop uses.
|
|
3. **Can it reach what it polls?** An access-rights error on a repository or an endpoint that plainly exists
|
|
is usually a rate-limited public path, not a credential.
|
|
4. **Has it already claimed this work?** A refusal to act twice looks identical to a failure to notice.
|
|
5. **Only then** the model, the prompt or the procedure.
|
|
|
|
# Where to look next
|
|
|
|
For endpoints, permissions or why something is refused, open the **Officer platform** skill. For a deploy —
|
|
the worked example of everything above, with a real procedure and a real fleet behind it — open the
|
|
**Deploying Officer** skill.
|