Docs/Reference

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).

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

StateOwner and directionHooks that run
All hooksOperator baseline; tama enforcement all restores the strongest machine policyEvery registered hook
Selected hooksOperator; tama enforcement only <hook-id>... narrows the machine baselineThe ids in hook-enforcement.json, plus ids enabled by the current session
Emergency bypassOperator; the confirmed emergency switch weakens policy to none at machine scopeOnly 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 enabledHookIds can 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 enabledHookIds only when its preflight and persisted override are bound to the active emergency state's exact, nonempty changedAt. The machine selection is bypassed, and the session record publishes that binding as emergencyChangedAt.

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:

  1. pauses every supervised session through agent-session-supervisor.py control pause --require-all; an exit trap resumes them after success, failure, or interruption;
  2. reinstalls the session controllers (--session-control-only) so session visibility survives the bypass;
  3. backs up ~/.claude/settings.json and ~/.codex/hooks.json into emergency-backup/ and empties their hooks maps;
  4. moves entrypoints aside to <name>.tama-disabled: the OMP agent hooks directory and every Git hook source the installed registry declares;
  5. stops the editor watcher (~/.shared-hooks/editor-hooks.pid, then the com.wisent.editor-hooks.plist LaunchAgent);
  6. records everything once in emergency-backup/manifest.json and writes hook-emergency-state.json (schema ai.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).