Reference
Conduct gates
The gates that keep a session's own conduct tied to its work: no cycle of gates that leaves no step, every call serving a claimed item, purgatory, reports that are not the work, repeated looks, operator messages kept as records, and the operator's stop order. Completion enforcement and the work registers are on Completion policy and work registers.
#Gates that leave no step
Each gate can look right on its own while the set refuses everything: the stop is refused because
work remains, and nothing the session does since changes anything, because every call it makes is refused
before it runs or it can only answer again. The core reads that from the session's transcript: the record
keeps where the transcript stood at each stop (transcriptBytes), and when a stop is refused by
the gate that refused the previous stop (the same refusal, or the same first refusing gate,
blockedHookId), the operator wrote nothing since, and no call attempted since ran (each was
refused before it ran, an error result with no details a tool that ran reports, or none was attempted),
the stop is let through with TAMA GATE CYCLE: the gates left this session no step …, naming the
stop refusal and each refused call, or that the session only answered again. The record keeps them under
cycle. A session that made a call that ran, or a stop after the operator wrote, keeps the
refusal: the refusal is given in full at least once before a cycle can end the turn. A transcript that
cannot be found or measured is said on standard error and the refusal stands.
Real journey tests/gate-cycle/release.rs.
A turn that ended on the provider's refusal of the request (the last assistant record has
stopReason: "error" and an errorMessage carrying
invalid_request_error) did no work, and sending it back resends the same request to the same
refusal. The core lets such a stop through whatever any stop gate decided, naming the provider's refusal;
the record keeps it as providerRefusal beside the gates' own decision. One rule
for every stop gate, read once in the core (tama-hooks session_record::provider_refused_request);
no gate carries its own copy. Real journey tests/gate-cycle/release.rs.
In a supervisor session block-wait-polling hands a reading loop the backlog. When the supervisor's last two
answers are the same text, or a call names an executor (its session key or its transcript) that an earlier
call already named while Oko recorded no instruction to it since (oko instructions), the call
is refused unless it changes a file or dispatches. A dispatch is an oko command naming
--defect or --owner, or one handing the executor an instruction in --text
(oko sessions resume|send|start) while Oko's register records that executor as owning an open
defect: the session commands name no defect, so the instruction binds the work the executor owns. An
instruction to an executor that owns none is refused naming oko defects assign <id> --owner
<executor>. The refusal reads BLOCKED: a supervisor dispatches; you read again … Assign the
next items instead: oko defects assign <id> --owner <executor>, then hand the executor its
instruction (oko sessions resume <executor> --text … [--model …]). Oko's unowned backlog for you: …,
carrying oko work backlog --supervisor <session> whole, or one line naming that command,
its exit status and the first line it said when Oko cannot answer it. A supervisor transcript line that is
no JSON record refuses naming its place. Each refusal is kept in
supervisor-loops.<session>.jsonl beside the session's control files. Real journey
tests/repeated-looks/gate.rs.
#Every call serves a task
block-unclaimed-calls refuses a tool call while the session holds no work: no open defect
Oko's register records it as owning, and no task or defect it declared it serves with
tama task scope set --session <session> --task <task-id> (or --defect), which
checks the entry against Oko's registers when it is written — Oko keeps task claims in its shared database,
too far to ask on every call. The refusal, BLOCKED: block-unclaimed-calls: this session holds no task and
no open defect …, names oko tasks claim <task-id> --by <session>, oko tasks
add <session> <n> --by <session>, tama task scope set, oko defects assign
<id> --owner <session> and oko work next. A holding session's call must state its
purpose in the harness's own field (OMP's intent i, Claude Code's description); one
that states none is refused naming the items it holds. A session must hold the item Oko's queue puts first
for it, oko work next --executor <session>: holding another one is refused with
BLOCKED: block-unclaimed-calls: the next item in Oko's queue for this session is …, naming that
item, why the queue puts it first and the oko tasks claim or oko defects assign that
takes it; the order changes in the register (priority, blockers), not by working on something else. While a
moment of the operator's frustration in the session (oko frustration list --session <session>)
has no cause named, every call is refused naming each moment and oko frustration cause <id> --cause
…, an Oko command, which passes. Each admitted call is kept with its items (each owned
defect, then the declared entry with the operator's words it was declared for), tool, purpose and time in
calls.<session>.jsonl beside the session's control files. A shell line made only of commands
of the configured Oko CLI or this Tama program — claiming, declaring the scope, reading an item, writing a
register — is never refused for want of work, so the gate does not close its own way out. On a machine where
Oko keeps no defect register and the session declared no scope the gate judges nothing: Tama runs without
Oko, and there is nothing to hold. A register, scope or record that cannot be read or written refuses with
its cause. Its declaration, hook-declarations/assignment-discipline/block-unclaimed-calls.json,
names every tool event; the gate runs once tama hooks declare --file enters it in the registry
(tama hooks declared says whether it has). Real journey tests/unclaimed-calls/gate.rs.
For a supervisor, block-supervisor-stop also lists the entries of Oko's review queue
(oko review, review.json in Oko's data, or OKO_REVIEW_REGISTER) that
its executors filed and nobody answered, each with the oko review answer <id> --text … --defect …
--by <session> that answers it.
#Purgatory: no tool until a self-criticism
block-tools-in-purgatory refuses every tool call of a session the operator holds in purgatory:
one whose answer he gave his worst verdict (a verdict:worst:<session>#<ordinal> ruling in
Oko) that no self-criticism of that session answers. It asks Oko
oko purgatory status --session <session> --json and, while the session is held, refuses
with BLOCKED, his unanswered verdicts in his words and the way out: write the self-criticism as
the text of an answer and, in that answer, run oko purgatory confess --session <session>,
which copies that answer from the session's own transcript. Only that command and
oko purgatory status --session <session>, each one plain command naming this very session,
are admitted; a confession for another session, a command riding along and output written to a file are
refused. A standing Oko cannot read refuses every other call with Oko's cause; a session without an
identifier and a machine without the Oko CLI are not judged. Its declaration,
hook-declarations/assignment-discipline/block-tools-in-purgatory.json, names every tool event;
the gate runs once tama hooks declare --file enters it in the registry. Real journey
tests/purgatory/gate.rs.
#A report is not the work it describes
block-stop-on-own-work refuses an ending that reports instead of working, with no
model: from the transcript, an ending said again after a refusal with no source changed, a plea
without one command on the clipboard or with one that cannot run, and a defect recorded with no
repair; from the answer itself, the turn-end attestation it carries word for word (no invented
limit, no decision handed back, no own work under blockers, and the rest). Every refusal and the
journeys that prove them are on their own page:
endings a report cannot replace.
#A command handed over is one already run
block-unchecked-operator-commands reads the final answer and the session's transcript,
with no model. A shell fence in the answer (no info string, or one naming a shell the system
declares) hands its lines to the reader to run, so each command in it must be one the session already
ran through a shell call: compared word by word from where the command proper starts, after
NAME=value prefixes and wrappers such as env, with a <placeholder>
word standing for any one word. A command never run is refused with
BLOCKED: block-unchecked-operator-commands: this answer hands over commands this session never ran
itself: and each line, so its first refusal (a revision not on main, an install whose format gate
refuses the revision) is met by the agent, and only a refusal that needs the reader is handed over,
quoted with the command. A fence for another language (```rust) is code to read and passes;
an unreadable answer or transcript refuses naming its cause. The declaration is
hook-declarations/answer-discipline/commands/block-unchecked-operator-commands.json; the gate runs
once tama hooks declare --file enters it in the registry. Real journey
tests/unchecked-operator-commands/gate.rs.
#A repeated look is not work
block-wait-polling reads the session's own transcript and keeps every look made since
the last change: a call with its arguments (OMP's intent label i left out) and the answer it
got. A change is a call that changes files, a git commit, or a message a person wrote to the
session; a harness continuation is not one. Making one of those looks again is refused with
BLOCKED: this exact call already ran at … and nothing this session did since could change its
answer … It was answered: …, quoting the earlier answer whole. No count and no window decide it:
the first look passes, the same look after a commit, an edit or a person's message passes, and the same
look in between is refused. block-idle-answer counts work after a stop refusal by the same
ledger: a change, or a look not made since the last change. A repeated look does not buy an ending.
Both read the transcript record by record: a complete line that is no JSON record refuses the call
with BLOCKED: the session transcript <path> cannot be read as records, so whether this
call repeats a look cannot be told: the line starting at byte <n> is not a JSON record: …
(block-idle-answer says the same on stderr and judges nothing), because a look a skipped
line held would go uncounted. Only a last line still without its newline, a record the harness is
writing, is set aside. The gate-cycle release, the stop-continuation reading and the provider-refusal
reading of the core read the transcript the same way.
In a session Oko records as a supervisor, executor transcripts (the harnesses' transcript roots),
Oko's data under ~/.oko and ~/Library/Application Support/Oko, and the answers
of oko commands are protected sources, read whole or not at all. A pipeline stage fed by
one, any other program handed a protected path, and a read with a selector or query on one are refused,
whatever program does the selecting, naming the full reads: oko sessions messages <session>
--since <time>, oko defects show <id>, or the read tool on the whole file.
An executor's session is not judged by this rule. The registry dispatches the gate on the shell and
process tools; the read and search tool rules take effect once its entry is dispatched on
read, grep, find and glob too. The real test is
tests/repeated-looks/gate.rs.
#Every operator message is tied to the work
block-unlinked-operator-messages refuses the end of a turn while any operator
message of the session is tied to no work. It runs
oko rulings unlinked --session SESSION_ID, which reads the session's transcript and
names, for each of the operator's messages, the task that carries it (once read, a message is a
task or is kept on the task in hand), the ruling recorded from it or the defect its task is
related to; a message tied to none of them — unread, or read before the session's first task — is
words no register keeps. Oko's own refusal is relayed, with the commands that link the message:
oko tasks kind SESSION_ID N <kind> --by SESSION_ID, oko tasks add SESSION_ID N
--by SESSION_ID, oko rulings record --session SESSION_ID --ordinal N --subject <task or
defect> --by SESSION_ID, or oko tasks relate <task> … <defect> for the
defect that names the rule and its mechanism (oko rulings enforce, oko rulings
audit). No model decides it, so the gate holds while no model answers. A stop payload
without a session is refused; an Oko that cannot be run is refused by name; a machine without
the Oko CLI has no register to read and the gate says nothing there. Program:
rust/crates/tama-hook-tools/src/programs/assignment/linked/block_unlinked_operator_messages.rs;
declaration: hook-declarations/assignment-discipline/block-unlinked-operator-messages.json;
real journey tests/operator-rules/unlinked.rs against the real Oko CLI, a transcript
and registers of the run's own.
#A claimed task is a promise
block-stop-with-claimed-work refuses the end of a turn while the session leads a task it
claimed in Oko's ledger and has shown nothing for. It asks oko tasks held --by <session> --json,
which answers from the shared ledger the claims with no evidence recorded and no verdict of done, and
refuses naming each task with oko tasks evidence <task-id> --by <session> --commit <rev>
--report <path>. An Oko that cannot answer refuses the stop naming the command and its answer;
a machine without the Oko CLI keeps no ledger and the gate says nothing there. The Oko that carries
tasks held is installed before the Tama that asks it. Program:
rust/crates/tama-hook-tools/src/programs/assignment/block_stop_with_claimed_work.rs; journey
tests/claimed-work/held.rs against the installed Oko and the signed-in ledger
(TAMA_TEST_HELD_SESSION).
#An operator stop order
No stop gate reads the operator's words for an order to stop: every gate above decides the same whatever the operator last wrote, so an order to stop coding does not lift a refusal that open work holds.
Oko keeps diagnosis and resumption in its own ledger. Five unsuccessful rounds
trigger diagnosis, not a demand for a person. The Tasks screen shows the same
cause, checked operations, resumption condition and next automatic check as the CLI.
Missing identity, transcript, ledger, or recognized task status prints
TAMA_HOOK_INFRASTRUCTURE: check-open-items-with-model: <reason>
and exits 70. Activation reports taskLedger: available,
completionAllowed and pendingTasks; open work is not an unavailable dependency.
The OMP adapter forwards an explicit decision: block. OMP must
keep that refusal beyond its eight-round advisory continuation limit and give
it priority over advisory continue: true results. Explicit user
interruption still stops the run; queued stop handlers must not restart it.
The main agent's answer in words that block-text-outside-tool-calls refuses
while work is open is cut off at its first streamed words: the adapter aborts
the run and, once the abort has settled, sends the gate's reason to the idle
session as the next turn. OMP runs that abort as an operator interrupt and
moves its run counter on, so a continuation sent while the run is still being
torn down would join the same run with no new start, and OMP would skip the
stop hook for every later ending of it; sent after the abort settles, it starts
a new run whose endings reach the stop gates again. A continuation that does not
start a turn is said in the hook progress line
(Tama: the cut answer's continuation did not start a turn: <cause>).
A run without a window keeps the stop refusal alone, so a run nobody watches is
not continued without end.
The hook release and OMP host are separate dependencies. Updating hooks alone
cannot repair an older host's continuation limit.