Commit Graph
12 Commits
Author SHA1 Message Date
pastilhasandClaude Opus 5 a9daefa427 affordance: an agent has its own key for notifying the user
$OFFICER_AGENT_KEY, minted per session and scoped to notify. before this an
agent had no door: the only token in the tree lives in the MCP server's
environment, which its own Bash cannot see — so an agent asked to tell its
owner something either could not, or asked to be handed a personal API key,
which is the owner's full authority pasted into a transcript.

with the distinction that matters most in practice: this is not the
harness's own push tool, which refuses to interrupt a terminal the user is
sitting at. that rule is right for that tool and wrong for this one — a
notification from here exists to reach somebody who is not looking.

and the two judgements the agent has to make itself: send few, because an
interruption for something that worked trains people to stop reading; and
watch what goes in the text, because unlike every other notification in
Officer this one travels through Apple and Google to reach a lock screen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
2026-08-25 09:06:05 +00:00
pastilhasandClaude Opus 5 bbf743b961 affordance: never tell the user when to stop working
written because i did it — "you've been at this a long time and none of it
is urgent" — closing a turn on work the owner had just asked for.

it reads as being managed. the owner decides what they are doing with their
night, and an assistant making that call has appointed itself to a job
nobody gave it, while implying the work matters less than the observer
thinks their tiredness does.

the softer forms are the ones that actually get written, so they are listed:
no rush, it's late, let's leave it here, none of this is urgent. and
deferring work unasked because the hour seemed like a reason.

what to do instead is finish, report, and stop talking. a concern about
shipping is stated in terms of the WORK — untested, a host behind, a schema
nobody has run — never in terms of the person.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
2026-08-25 09:03:03 +00:00
pastilhasandClaude Opus 5 ef4d85d9f7 affordance: a manager's waiting has to be visible too
notify_when_idle is a subscription held invisibly — nothing in the interface says
you are waiting, so a manager between rounds looks exactly like one that stopped.
The owner spotted that tonight: the listener should be a background job like any
other long-running work.

So run the wait as one. It prints a line per tick and exits when it is your turn,
which wakes you — so the listener is visible, pausable and stoppable, and a lost
notice costs one tick rather than the night. Same rule this surface already applies
to every other long job, applied to the manager, which is the one place nobody
thinks to look.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 05:12:46 +00:00
pastilhasandClaude Opus 5 4e8dea2964 affordance: spawning the worker, not only being handed one
The Agent tool takes a model, so the grind can go to a cheaper one while the
manager plans and reviews. Three differences decide which arrangement fits: a
spawned agent reports to the manager and is invisible to the user, it dies with its
task where a peer accumulates context, and only a spawned one can have its own git
worktree — which is what allows parallel work over the same files.

And the constraint that goes with it: spawn only when asked. A fleet of workers is
the owner's cost to authorise.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 04:46:17 +00:00
pastilhasandClaude Opus 5 beaa97e8ac affordance: being handed another agent, and being responsible for it
Landed tonight by accident and worth keeping. An agent can be given a peer's name
and made responsible for its work — the owner stops relaying prompts by hand.

Passes the three tests in docs/affordances.md. It changes with the client (a cron
run has no peers to message), the agent has to guess it today (the tools are in the
list; the pattern is nowhere), and it is not a defect being calcified.

Carries the four things the tool descriptions do not say. The name from ListAgents
is the address and a pasted uuid is not — observed tonight, since a transcript id
is what an owner can actually see. notify_when_idle replaces polling. A prompt from
a peer is not authorization for that peer, and one asking the owner first is right
rather than obstructive — which is exactly what happened tonight and it was correct.
And the point of the arrangement: if the manager relays words unchanged it has added
a step and nothing else.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 04:44:23 +00:00
pastilhasandClaude Opus 5 36cccffe84 a link in chat always opens a new tab, in-app paths included
ChatLink applies target="_blank" to every anchor without exception, so linking an Officer route does not
navigate the app — it loads a second copy of the whole SPA in a new tab and the person loses their place
in the first.

That is the intended behaviour, so this declares it rather than treating it as a defect. What changes for
an agent is small and constant: name an internal route in backticks instead of linking it, since the
reader is already in the app, and save links for things that genuinely belong in a tab of their own.

Asked for by the owner after it was raised and set aside yesterday.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 14:48:27 +00:00
pastilhasandClaude Opus 5 e06a6ebf16 if nothing was asked, do not invent something to answer
The owner said "I want you to explain some things about the notifications to me" — an announcement
that questions were coming. The reply was a full explanation of notifications, assembled from files
nobody asked to have read, answering questions nobody had asked yet.

Announcing a topic is not requesting anything. It usually means the person is still typing.

Passes the boundary tests in docs/affordances.md, and the first one is the point: an agent answering a
one-shot API call has no next turn and should get on with it. Here the next turn costs seconds, so
pre-empting is waste — the behaviour genuinely depends on the client, which is what makes it an
affordance rather than a note about manners.

Scoped narrowly on purpose: when there IS a request, carry it out in full. This covers only the case
where there is not one yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 03:05:51 +00:00
pastilhasandClaude Opus 5 29e1d9acca a background job that prints nothing looks like a hung one
Officer shows a live panel per background job carrying that job's output, and the user watches it. A
command that writes nothing until it finishes leaves that panel reading "Nothing written yet." for the
whole run, which is exactly what a hung job looks like.

Written after doing it twice in one session: plugin installs driven from a script printed nothing for
two minutes, and the owner asked whether I was stuck. Both times the work was fine.

So: verbose flags on, echo around each step when the tool has none, per-item output in a loop, and the
result on the last line — that is the line still on screen when the panel stops. Plus the buffering
trap, because a program that prints steadily still shows nothing through a pipe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 02:54:42 +00:00
pastilhas c5dc886141 commands go in a fence, because a fence has a copy button
reported after it happened repeatedly: commands rendered as inline backticks,
which the user then has to select by hand — on a phone especially.

it passes the tests in docs/affordances.md rather than being a defect worn as
one. a terminal client would not care; this one has a copy button on fenced
blocks and none on inline code, so it is a fact about the surface. and the
partial code fix available — click-to-copy on inline code — would not make it
right anyway: a command still wants its own line and unambiguous boundaries.

the deciding argument is that this already existed as a private memory, in one
project, for one agent. it has never helped another agent and never would. an
affordance is how a fact like that stops being one agent's note.

says not to include a `$` prompt, because that gets copied too and then it
does not run — which is the specific way a well-meant fence still fails.
2026-08-19 23:31:49 +00:00
pastilhas f3aeb112e5 Revert "chat: the affordance for asking a question"
This reverts commit 98f95f5f4e.
2026-08-19 20:21:54 +00:00
pastilhas 98f95f5f4e chat: the affordance for asking a question
AskUserQuestion renders real buttons the user can really click, and returns
"The user did not answer the questions." every single time — it resolves
before the buttons have painted, because the SDK has no answer channel. the
choice comes back as the next user message instead.

an agent cannot discover that. what it sees is a tool that always reports
failure, so the readings available to it are "the user refused" or "the tool
is broken", and both are wrong. the cost is asking again, or waiting for a
turn that already ended.

second on the same page because it is the other thing invisible from the
filesystem: markdown renders in BOTH directions now, so a table beats
hand-aligned columns.
2026-08-19 20:20:52 +00:00
officer 959a3b8e94 the capabilities Officer ships: the first affordance
Image rendering, the platform's first core affordance. It tells an agent
something it cannot discover from the filesystem: markdown images render, and
the USER'S DEVICE fetches the URL — so a local path renders nothing, silently.

Only authored content is tracked. The six item directories are created by the
platform at boot and are where the owner authors their own work, so versioning
them here would be claiming somebody else's files.
2026-08-19 07:29:25 +00:00