Reference
Enforcement control
Tama has three machine enforcement states, and their ownership is explicit. The normal baseline runs every registered hook. An operator can narrow that baseline to a durable machine-wide selection, or use the confirmed emergency switch to bypass it completely. A live session can only add hooks to the machine baseline; it can never remove one. There is no per-session disable (core-contracts).
#The three machine states
| State | Owner and direction | Hooks that run |
|---|---|---|
| All hooks | Operator baseline; tama enforcement all restores the strongest machine policy | Every registered hook |
| Selected hooks | Operator; tama enforcement only <hook-id>... narrows the machine baseline | The ids in hook-enforcement.json, plus ids enabled by the current session |
| Emergency bypass | Operator; the confirmed emergency switch weakens policy to none at machine scope | Only generation-bound ids explicitly enabled by the current session |
The durable selection remains recorded during an emergency bypass, but it
enforces nothing while that bypass is active. Re-enable removes the bypass
and the selected machine policy takes effect again. Missing
hook-enforcement.json means all hooks; malformed or unreadable state
reports the failure and falls back to all, never none.
#Session records
Supervised runtimes publish one file per live session under
~/Library/Application Support/Tama/session-control/ (directory mode
0700), schema ai.wisent.tama.session-control.v2. A record carries
agentId, sessionId, a 64-hex controlKey, cwd, livenessMode
(process — pid names the live owner process from
TAMA_SESSION_OWNER_PID — or heartbeat — updatedAt within the
heartbeatTTLSeconds the launcher stated in TAMA_SESSION_HEARTBEAT_TTL_SECONDS
— or unknown, when the launcher stated neither, with livenessError saying
so, or naming the variable whose value is not a positive whole number or not text
(TAMA_SESSION_OWNER_PID is "abc", not a positive whole number);
Tama Desktop lists such a session with that sentence and never controls it), globallyDisabled,
disabledHookIds, enabledHookIds, the override's
emergencyChangedAt generation binding, the session's capability (issuer,
nonce, release and checksum binding, lifetime, expiry, remaining uses, and
per-tool grants), runtime status (installedReleaseId, loadedReleaseId,
catalogChecksum, registered/loaded hook counts, unknown hook ids,
enforcementMode, machineEnabledHookIds, reloadRequired,
reloadPending, registryLoadError), and
semanticRuntime — the sequence of recent events with each decision, the
blockedHookId, and the reason string. Tama only reads these records;
invalid, legacy-v1, or stale ones are ignored.
#Reminders after every tool call
The remind-open-work PostToolUse hook follows each tool result with a
sentence the operator set, which the agent reads as context after every call.
tama sessions remind --session SESSION_ID --text "SENTENCE" sets one
session's sentence, given after every call while it is set;
tama sessions remind --default --text "SENTENCE" sets the machine-wide
sentence, given only while Oko holds an open task or defect for the session.
--clear removes either; the command without --text or
--clear prints the current sentence, and --json prints
{"scope", "sentence"}. Both live in
session-control/reminders.json, schema
ai.wisent.tama.reminders.v1. No sentence is compiled into Tama: with
none set the hook adds nothing. Naming both or neither of
--session and --default, or both --text and
--clear, is a usage error (exit 2). The hook gives the agent the
sentence; whether the agent repeats it in its own words is not checked.
#The operator's rules after every compaction
A compaction replaces a session's earlier conversation with a summary, and the
operator's standing rules go with it. The inject-operator-rules hook
runs on session_start:compact and answers with his standing rules
(oko tasks rules --all: every message Oko's ledger reads as a rule, from
every session, in his words, with the session and message number) followed by his
rulings on tasks and defects (oko rulings). In OMP the adapter puts that
text into the session as a displayed custom message right after the compaction:
it steers the turn the compaction resumes, and is appended to the conversation
when the session is idle. When the hook refuses or cannot be dispatched, the
same message carries Tama's session_start:compact hooks refused or failed:
and the reason. An unavailable rule ledger or an unreadable ruling register makes
the hook exit unsuccessfully with TAMA_HOOK_INFRASTRUCTURE and its actual operation and cause;
no partial context is emitted. The adapter's failure message proves neither restoration nor prevention of continuation. The same
delivery carries a passing user_prompt_submit hook's context ahead of the
turn the prompt starts. A message becomes a standing rule when the session that
received it records it with oko tasks kind <session> <number> rule --by
<session>. The operator says a rule again whenever a session forgets it, so
the ledger holds one text under many sessions, and a later saying rewords it by
inserting words into its sentences; the block gives each instruction once, as
its fullest text, under the newest message that carries that text, as
- <time> (<session> #<number>, said N times since <time of the
oldest>): <text>, where N counts every saying of every variant. A text
is a variant of a fuller one when every word of it stands in the fuller text,
word for word and in order, with only words inserted between them; the variant
is not written again, and every word of it stands in the text kept. A text said
once has no count. No text is cut: a rule is lost by cutting it, not by saying it
once. A ruling whose words are a standing rule's, or words a standing rule carries
that way, is listed as the same words as a standing rule above or
words a standing rule above carries in full instead of copied.
tama assignment show prints the same block. The journey
tests/operator-rules/carried.rs runs the hook against a ledger fixture
of three sayings of one instruction.
A rules report that is not an array is refused with
Oko tasks rules --all --json returned a non-array rules report.
A missing or wrongly typed rule field reports
Oko tasks rules --all --json rule at index <index>: <decoder error>
as the hook's infrastructure failure, without a successful context payload. Tama does not replace unreadable rules with
an empty list or invent their source session, message number or text. See assignment restoration and its real qualification contract.
#Session-scoped enable
The desktop writes a private request file
<controlKey>.<requestId>.request.json (mode 0600) into the same
directory — operation set-hook with hookId and enabled: true, or
enable-all — and reads the matching
<controlKey>.<requestId>.response.json again each time the
directory changes, until the response is there or the session's process has exited. The
supervisor validates the envelope against the exact session identity tuple,
applies accepted state atomically, and answers with the authoritative session
record; rejection, malformed identity or session exit is an
actionable failure, never optimistic success.
Enablement semantics compose the machine state and the session state:
- normally, every hook in the all-hooks baseline runs;
- in machine selection mode, the selected ids run and
enabledHookIdscan add hooks for this session, but cannot remove a machine-selected hook; - during a global emergency disable, one-hook enable may add the id to
enabledHookIdsonly when its preflight and persisted override are bound to the active emergency state's exact, nonemptychangedAt. The machine selection is bypassed, and the session record publishes that binding asemergencyChangedAt.
The one-hook allowlist and generation binding are retained in the private
override and OMP session history, so they survive a reload or resume of that
same session while the emergency generation is unchanged. A fresh global
disable changes changedAt and invalidates prior or legacy allowlists and
capabilities. The supervisor clears them before publishing state; neither the
desktop nor tama sessions treats a stale id as enabled.
enable-all is different. Outside emergency disable it binds an override to
the installed release and catalog checksum and awaits the shared runtime reload;
concurrent identical requests coalesce. A failed state update restores the
preceding override. While global emergency disable is
active, enable-all is refused with
global-emergency-disable-active; it cannot turn the emergency allowlist
into every hook.
The Session screen and Hooks inspector use the same live facts:
during emergency disable, per-hook Enabled means membership in the
generation-validated enabledHookIds. Otherwise it means membership in the
all-hooks or selected machine baseline, plus any session enable. A rejected
mutation remains a visible failure rather than changing the displayed status
optimistically.
Inside an OMP session, the bundled adapter exposes the same control plane as
session tools: tama_hook_runtime_status, tama_set_session_hook,
tama_enable_all_session_hooks, tama_request_session_capability, and
tama_reload_hook_runtime. Session hook disablement is prohibited there
too.
#Common hook runtime and harness adapters
Tama owns registry loading, hook execution, enforcement selection and reload, and every harness reaches the same native execution loop through an adapter that only translates events and decisions. The protocol, the rejected-replacement behaviour, the observed state and the OMP integration status are on their own page: common hook runtime and harness adapters.
#Completion enforcement
The completion policy and work registers page documents supervisor and executor roles, per-item waits, task qualification, source-before-build ordering, operator stop orders and real completion journeys; how the build gate reads open work says where the build gate reads tasks and defects from and how it refuses when it cannot.
#Objective-authority diagnostics
An explicit transcript_path identifies the authority source:
an unreadable path is not replaced by another session transcript. Objective
review reports the failed open, read or JSON decode with that path and the
actual filesystem or parser cause; malformed records are not skipped to
produce a partial user contract. Without an explicit path, session transcript
discovery reports directory, entry and metadata failures rather than treating
them as no conversation. An absent session identity and an empty or unreadable
transcript-root setting are separate refusals.
Stop reviewers propagate the same context failures through their normal
infrastructure-refusal path. They do not review the last answer against an
empty substitute contract or silently allow completion after context
serialization fails. The real transcript journey exercises both the objective
authority hook and check-stop-asking, which reads the transcript
for the answer's turn-end attestation and asks no reviewer, for filesystem and
decode refusals.
A stop reviewer decides through a schema, not a line of prose: it reads the user's own messages and the ending answer and returns pass or block with its reason, bound by the decision schema Brama's typed decisions answer. An answer outside that schema, or a reviewer that cannot answer, is an infrastructure refusal with its actual cause, never a finding that the answer broke the rule. A machine that declared no model asks no reviewer: the session's recorded attestation decides instead.
tama hook block-objective-substitution exposes these causes in
its structured refusal before requesting a reviewer. The registered
objective_transcript journey under tests/completion/
uses genuine absent files, directory targets and malformed persisted JSON,
retaining the declared candidate, executable digest and actual hook output.
Its recovered-file case checks the separation of user authority and tool
evidence and requires a real reviewer to admit the authorized read. Run the
registered journey with TAMA_TEST_SOURCE_REVISION naming the clean
candidate and the actual TAMA_BRAMA_TOKEN_FILE,
TAMA_OBJECTIVE_AUTHORITY_MODEL_ROUTER_URL and
TAMA_OBJECTIVE_AUTHORITY_MODEL_ROUTE; an unavailable reviewer is
a failed run, not a passing recovery. Direct-hook qualification does not prove
that the machine has selected the objective hook.
The separate objective-authority hook reads no vault item. It presents a
Skarbiec-issued token for its own consumer, tama unless
TAMA_BRAMA_CONSUMER names another, from the owner-only file
~/.stado/<consumer>-skarbiec-token or the file
TAMA_BRAMA_TOKEN_FILE names. Brama learns the client by
introspecting that token at Skarbiec, and the token's
call:brama#<route> grants decide which routes it may ask for.
The route is best unless TAMA_OBJECTIVE_AUTHORITY_MODEL_ROUTE
names another. Brama's address is TAMA_OBJECTIVE_AUTHORITY_MODEL_ROUTER_URL,
STADO_MODEL_ROUTER_URL or BRAMA_URL when one is set, otherwise
the adapter this machine's resolver publishes for Tama's consumer, otherwise the
route stado service directory connect brama --consumer tama answers; no
address is kept between requests and no public hostname is put in its place
(how the address is resolved). It does not inherit
the operator's credential-store identity.
An activation failure records the process outcome before its captured
diagnostic: could not start command, terminated by <signal> or
exit <code>, as activation-preflight-failed:<hook>:<outcome>: <output>;
a caller that abandoned the activation reads
activation-preflight-cancelled:caller-abandoned. A missing, empty or malformed token
file is refused with the consumer, the file path and the
skarbiec token-mint command that issues one; no token is included.
tama model-provider check also asks this machine's Skarbiec whether the token
holds call:brama#<route>, which separates a missing local grant
from a gateway that answers to another authority.
#Global emergency disable
The bundled emergency_disable_hooks script is the switch; the desktop runs
it from Posture (confirmed as Disable every Tama hook on this machine)
and re-runs it with TAMA_EMERGENCY_ACTION=enable for re-enable. Disable:
- pauses every supervised session through
agent-session-supervisor.py control pause --require-all; an exit trap resumes them after success, failure, or interruption; - reinstalls the session controllers (
--session-control-only) so session visibility survives the bypass; - backs up
~/.claude/settings.jsonand~/.codex/hooks.jsonintoemergency-backup/and empties theirhooksmaps; - moves entrypoints aside to
<name>.tama-disabled: the OMP agent hooks directory and every Git hook source the installed registry declares; - stops the editor watcher (
~/.shared-hooks/editor-hooks.pid, then thecom.wisent.editor-hooks.plistLaunchAgent); - records everything once in
emergency-backup/manifest.jsonand writeshook-emergency-state.json(schemaai.wisent.tama.hook-emergency-state.v1,disabled: true,changedAt).
Repeating disable is idempotent (Tama hooks are already disabled.). The
desktop treats the machine as disabled only when both the manifest and the
disabled state file exist, and after the script returns it re-reads that
durable state — a switch that did not persist is reported as a failure, not
claimed as success.
#Re-enable
Enable refuses to run without the emergency manifest and a valid bundled
release. It performs the full transactional installation of
hook-releases with --emergency-manifest, which
restores every moved entrypoint from the manifest, regenerates provider
configs from the emergency backups, re-bootstraps the editor LaunchAgent,
and only then removes the emergency state and manifest. A failed enable
preserves the recovery evidence and does not claim the enabled state.
Settings → Deactivate is the composed teardown: it runs the same global
disable and then unregisters the privileged daemon, System Extension, and
network filter independently, so one failure does not skip later cleanup
(desktop). Restoration evidence under
~/Library/Application Support/Tama is deliberately preserved
(operations).