the commander rules are general; deploying is an instance of them
The rules had leaked toward the one commander that exists. Audited by grepping the commander skill for deploy vocabulary rather than by rereading it, which found five places where a general rule was written in terms of the specific job it came from: - the baseline rule's headline example was "watch for a new tag" — the one case, standing in for all of them. Now "watch for new X, whatever X is: a tag, a file in a folder, a row, an unread message, an alert". - the loop "cannot weigh a host" → cannot weigh a situation. - OFFICER_ROOT being empty "makes every release look permanently undeployed" → makes anything the loop writes to record its own progress land where nothing reads it, so a watcher has no baseline and acts on the same work for ever. Same defect, stated as what it costs any watcher. - "a procedure in a file makes the deploy repeatable next month" → makes the same job repeatable, and reviewable by somebody else. - the local-address rule was fleet-specific AND duplicated what the deploy skill already says. Generalised to "reach what you are watching by the shortest path available", with the Officer-specific instance left where it belongs. The cross-reference now names the Deploying Officer SKILL rather than a file path, because a skill has a description in the index and a file path has to be known about first. What the audit was checking: a user describing something that has nothing to do with releases must get the same rules — baseline, claim once, ask before arming if the condition is not clear, spawn for judgement, say what you did not do. Those were always general. The examples around them were not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+14
-13
@@ -28,7 +28,7 @@ from inside it.
|
||||
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 host, or write a report. So
|
||||
**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.
|
||||
|
||||
@@ -45,7 +45,7 @@ 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
|
||||
deploy repeatable next month. A prompt typed into a dialog is not.
|
||||
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:
|
||||
@@ -53,8 +53,8 @@ if it is money, or a duplicate message, or a half-applied change, the action mus
|
||||
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 a new tag" is not a
|
||||
condition. A watcher armed today sees everything that already exists, and unless something says otherwise it
|
||||
**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.
|
||||
|
||||
@@ -84,8 +84,9 @@ 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 a lock file lands somewhere nothing reads and every release looks permanently undeployed. Set it
|
||||
explicitly in the loop.
|
||||
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.
|
||||
@@ -106,10 +107,10 @@ Four details the mechanics do not carry, each of which was learned by getting it
|
||||
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.
|
||||
- **Poll a local address, never a public hostname.** Where the thing being watched is on the same machine or
|
||||
the same tailnet, reach it directly. A public hostname through a reverse proxy shares one source IP with
|
||||
everything else behind it, and rate limiting there refuses connections for long stretches — which shows up
|
||||
as an access-rights error and is not one.
|
||||
- **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.
|
||||
@@ -127,6 +128,6 @@ In this order, because each rules out the one below:
|
||||
|
||||
# Where to look next
|
||||
|
||||
For endpoints, permissions or why something is refused, open the **Officer platform** skill. For a deploy
|
||||
specifically, `prompts/deploy-commander.md` in the platform repository is the worked example: a real
|
||||
procedure, with the reasoning for each rule written beside it.
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user