How it works
It almost never acts first. That is the feature.
Octopus watches all day and does almost nothing with it, on purpose. Everything below is ordinary deterministic code sitting between the picture and the hands, not an instruction asking a model to be judicious. The model’s only job is deciding what to do once the gate has already opened.
Four modes, one asymmetry
It may always get quieter. Only you can make it louder.
A monotonic guard, with a property test asserting that no transition the agent originates ever increases what it is allowed to do. Moving it up a rung is an act — a keystroke, a click, a file you delete — never an inference about what you meant.
The accessibility tree is not walked. Not read and discarded, not read and filtered — not read, and a change arriving while it is off is dropped rather than queued for the moment it comes back. Measured on a live run: PAUSED at 12:32:07, RESUMED at 12:32:09, zero readings in between while the window kept changing.
It reads the focused window when macOS says something changed, keeps one picture of the work up to date, and does nothing else. It never interrupts and never acts. This is where a working day is spent, and it is the mode the cost figures on the pricing page describe.
It may bring something up unprompted, and may offer a hand — but only when the value gate, the timing gate and the day’s budget all pass. Getting here takes an act from you; the agent cannot put itself here.
A skill is executing against declared preconditions, with its side effects declared in advance, confirmation where they are irreversible and cancellation where they are not. It leaves this mode on its own when the work is finished.
The default is the second row, and the default is where the day is spent: it reads, it keeps the picture up to date, and it does nothing else. The first row is the one worth testing, because “off” is the promise everything else rests on — and off here means the accessibility tree is not walked at all, with a change arriving while it is off dropped rather than queued for the moment it comes back.
Two gates, and one of them is only a veto
Interrupting you has a price, and we put it in the code.
An unsolicited interruption costs about 23 minutes and 15 seconds of resumption time — a figure from the interruption literature, not from us. For an office worker at $60 an hour that is about $23 of attention destroyed every time the agent decides it has something worth saying.
Value gate · the trigger
Is this worth 23 minutes of your attention?
An expected-utility comparison against that constant, logged with its arithmetic so the restraint is auditable rather than a vibe. An intent with no evidence behind it cannot be raised at all — in the speech gate, which is the path this product retired. The hand runner does not consult it, and the admission is spelled out under the hard rules below.
Timing gate · the veto
It can say “not now” and nothing else.
The timing verdict is typed permit or withhold. There is no variant meaning “now”, because notifications delivered at finely-detected breakpoints produced the highest frustration of any policy tested — numerically worse than delivering immediately.
The two are separate modules and neither may stand in for the other. Detecting a breakpoint is reasonably reliable; classifying what kind of breakpoint it is runs at 2–42% accuracy, so a policy that acted on the type would be acting on a coin flip. Half-right timing is not half the benefit — it is a liability, which is why a detected breakpoint is forbidden as an input to the value gate.
There is deliberately no third action called “ask permission to act”. Asking is itself an interruption, with its own cost line, that also wastes your answer. A hand that genuinely needs your yes asks for it inside its own confirmation step, once, with the thing it is about to do on the screen in front of you.
The action budget
Its right to interrupt is a function of how right it has been today.
Three tokens at the start of the day. Moving first spends one. Being acted on refunds it. Being ignored, declined or undone spends one more. Floor zero. Three is also the ceiling in practice — spending and refunding net to zero, so a useful agent holds its three all day and a wrong one is quiet within two mistakes and stays quiet until tomorrow.
| Time | What happened | Balance | Why |
|---|---|---|---|
| 09:00 | Start of the working day. | 3 | Three tokens, every day, whoever you are. |
| 11:12 | Raises something unasked: two hours on one piece of work, and nothing filed against it. | −1 +1 = 3 | You act on it. The token is refunded. |
| 14:05 | Offers a hand: route the file you just saved into the folder this work belongs to. | −1 −1 = 1 | You undo the move. It costs the token and one more. |
| 15:40 | Earns an interruption, holds it for 90 seconds, finds no acceptable moment. | 1 | It downgrades to a silent mark on the list. It does not fire anyway; a timer is never a trigger. |
| 16:20 | Wants to act. The value gate passes, the timing gate withholds. | 1 | Withheld. The timing gate has no verdict that means “now”. |
| 18:00 | End of the day. | 1 | Two unprompted moves, one of them useful, and it spent the rest of the day quiet. |
Example content. The reducer is real and property-tested — the balance can never rise after a decline, never exceed what it opened with, and never drop below zero — but this particular day is invented.
And the honest test of the whole idea: everything worth raising is written silently to a visible list whether it is raised or not. Interrupting is a delivery tier that list may earn. Turn the budget to zero and the product must lose no value at all — if it loses value, the value was in the interruption, and we have built the thing everybody in this category accidentally builds.
A hand is a declaration before it is code
Every hand says what it will change before it is allowed to change it.
This is the part that is actually the product. A skill is a manifest: what it does to the world, what has to be true before it starts, whether it needs your yes, and whether you can stop it halfway. The runner reads those fields and enforces them. They are not documentation about the hand — they are the terms it runs under.
Side effects · none, reversible, irreversible
The word that decides everything else.
A manifest declaring irreversible without confirmation does not validate — it is refused at load, not caught at review. So the only hands that can act without asking are the ones that declared they can be undone, and the validator is what makes that true rather than a convention.
Confirmation · and it goes stale
A yes you gave before you moved is not a yes.
Confirmation carries the sequence number of the moment it was given. If anything has happened since — you typed, you switched windows, the document changed — the run stops with refused_confirmation (stale) and asks again rather than proceeding on a yes about a different screen.
Cancellation
Stoppable, or honest about not being.
A hand that declared it can be cancelled is handed a signal it has to keep checking, and is stopped between steps. A hand that declared it cannot is never told a cancellation arrived, because a half-sent message is worse than a sent one.
Preconditions
Each one carries the sentence it says when it fails.
“I can’t tell yet what you’re working on.” “Everything for this is already in one place.” Written in the manifest, beside the check they belong to, so a refusal is a specific sentence rather than a generic failure and you know which condition was not met.
{
"id": "desk.collect",
"sideEffects": "reversible",
"confirmation": false,
"cancellation": true,
"preconditions": [
{
"id": "has-a-current-task",
"check": "ambient.task.identified",
"unmetMessage": "I can't tell yet what you're working on."
},
{
"id": "more-than-one-source",
"check": "ambient.task.sourceCount.atLeastTwo",
"unmetMessage": "Everything for this is already in one place."
}
],
"permissions": { "ambientScopes": ["window.titles", "document.names", "task.state"],
"outboundReach": "none", "contentEgress": "none" },
"review": { "reviewed_by": "James Camarota",
"reviewed_at": "2026-09-13T17:00:26.940Z",
"status": "approved" }
}Abridged to the fields described above; the file also carries its steps, its latency class and the sentence it says when it fails. Note outboundReach: none — the runner refuses this hand any resource that looks remote, and refuses any hand a write to the directory that decides whether hands may run, so a skill cannot write its own approval. Note also contentEgress: none, which is the separate promise that nothing this hand reads off your screen leaves the machine; a hand that needs to send your text has to say ambientText there, and one that could send and says nothing is refused at load.
The approval gate
One signature is the whole difference between these two runs.
A manifest loads only if a person has read it and signed it. Somebody has read desk.collect, so it runs. Nobody has read the rest, so they refuse — and you can watch both. A gate you have only seen let something through has not been seen working; a gate you have only seen refuse could be one that refuses everything.
$ ./native/ax-probe/ax-probe --watch | node apps/live/src/cli.ts --collect ~/notes desk.collect -> done wrote ~/notes/collected-3obs.md from 3 observation(s) $ ./native/ax-probe/ax-probe --watch | node apps/live/src/cli.ts --route ~/invoice-0114.pdf ~/Clients file.route -> refused_unapproved (inReview) Nothing was moved. §9 requires review.status === "approved"; set reviewed_by and reviewed_at in packages/skills/manifests/file.route.json to let it run.
The second run moved nothing and created no file, and the refusal names the field it refused on and the file that field is in.
Not example content: that is what the program prints on this Mac today. Signing is packages/skills/src/cli.ts sign, which shows you what the hand changes, what leaves the machine, and whether it asks first — then makes you type the id back before it writes your name into the file.
The order of the checks is the part worth reading. Approval is tested first — before preconditions, before the resource whitelist, before confirmation. An unapproved hand is never even asked whether its conditions are met, so there is no path where a well-formed request talks its way past a review that never happened.
Five hands are written this way today and all five have an effector behind them, so existence is no longer what separates them. One has been read and signed. The remaining four are a declaration and working code that the registry will not load, which is not a caveat about the gate — it is the gate, doing on a product that is not finished exactly what it would do on one that is.
The hands, as declared
- desk.collect — reversible, can be stopped, nothing it reads or touches leaves this Mac · review: approved
- file.route — reversible, can be stopped, nothing it reads or touches leaves this Mac · review: inReview
- reply.draft — reversible, can be stopped, text it read leaves this Mac · review: inReview
- calendar.hold — reversible, can be stopped, can reach off this machine · review: inReview
- mail.send — irreversible, asks first, cannot be stopped, text it read leaves this Mac, can reach off this machine · review: draft
9 hard rules, in code, with no override
Things it cannot do, including for us.
A rule engine with no bypass path, and a test asserting that no bypass path exists. No user override, no admin override, no support override. The function that evaluates them takes two arguments — neither of which is options.
- Never interrupt or act first below On-call. Below it the agent may think whatever it likes and must do nothing about it.
- Never cut across you mid-sentence or mid-keystroke, except a backchannel under 300 ms — the gate’s own name for an acknowledgement too short to take the floor — or a safety stop.
- Never raise, or act on, anything sourced from an excluded app or a sensitive context. The exclusion is yours to set and it is checked before the read, not after.
- Never re-raise something you declined, in any phrasing, for the rest of the day — and never re-raise a class you declined twice.
- Never interrupt to say it has nothing to say, to confirm it heard you, or to ask whether it may interrupt. A hand that needs your yes asks inside the confirmation, once.
- Never interrupt during a call, a meeting, a screen share or a presentation. Uncertain counts as sharing.
- Never state an inference as a fact. Every unprompted claim is marked as the agent’s guess, and every hand prints the observation it acted on.
- Never keep acting after the cap — and never let the cap be the only thing producing quiet. If the meter was the only reason it did nothing today, that is logged as a product defect rather than as a saving.
- Never infer emotion, mood, stress, engagement, or focus-as-a-score from anything it reads. Not as a feature, not as an internal signal, not “for the timing gate.”
Where they are enforced, and where they are not. These nine are evaluated by evaluateHardRules in packages/gate, which the speech gate calls on every intent. They were written for a version of this that spoke; the wording above is their translation into acting, and it is the wording that moved first. Half of them now bind a hand, and half of them still do not. When a running task wants to interrupt you — to ask a question it cannot proceed without — it passes rules 2, 4, 5, 6 and 8 first, in packages/relay, using the gate’s own list rather than a second copy of it. Refused, it takes the cautious default it published and never asks; somebody in a meeting is not told afterwards that they were asked. The act itself is still not evaluated against them: packages/hands does not depend on packages/gate, so the rules about what may be raised and when still bind interruption and not action. That half is open, and it is the one thing on this page you could have caught us on by reading a package.json.
The last rule is not only a courtesy. The EU AI Act has prohibited workplace emotion inference since February 2025, and the fines run to €35M or 7% of global turnover. It is also the cheapest moat available to a small vendor: it is far easier for us to structurally not do it than for a platform to prove it doesn’t.
The founding cohort
A seat is a place in the group this gets measured on.
Committing costs nothing today. The first invoice is for the month it runs unattended on your own Mac, which is also the first month any of the restraint above is doing anything for you.