The intake section named a `deploy/` folder as what to look for, which is a platform-shaped thought in a file that has to work for a mailbox, a drop folder, a document library or a machine that runs one script. An example that specific reads as an instruction. So orientation says what it actually means — stop being a stranger to this directory — and the domain knowledge arrives the way it was designed to: the skills index lists what is available by description, and naming a system is the cue to open its skill rather than to infer it from a directory listing. Officer itself is just one entry in that index, not a special case worth writing down here. And if there is no skill for what they named, say so and ask. That is a real answer; inventing a mechanism to fill the gap is not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
186 lines
12 KiB
Markdown
186 lines
12 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.
|
|
|
|
**Whatever the job is, 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 you get to questions two through four will be shaped by the answer to the first
|
|
anyway.
|
|
|
|
**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.
|