Docs/Reference

Reference

Build and restart registry: block-unregistered-build-or-restart

A gate on pre_tool_use:bash (tama hook block-unregistered-build-or-restart) that refuses a build without an unused, unexpired entry recording the agent's statement that the whole task is finished. Each accepted build consumes its entry, so the next build needs a new statement and intent. A restart still needs an open entry but no task statement. Refusals start BLOCKED: unregistered build: or BLOCKED: unregistered restart:, go to stderr and exit 2 before the command runs. The registry is ~/.shared-hooks/build_restart_registry.json; tama build and Tama Desktop share its writer.

A gate on pre_tool_use:bash (tama hook block-unregistered-build-or-restart) that refuses a build without an unused, unexpired entry recording the agent's statement that the whole task is finished. Each accepted build consumes its entry, so the next build needs a new statement and intent. A restart still needs an open entry but no task statement. Refusals start BLOCKED: unregistered build: or BLOCKED: unregistered restart:, go to stderr and exit 2 before the command runs. The registry is ~/.shared-hooks/build_restart_registry.json; tama build and Tama Desktop share its writer.

Availability: this completion check is in source, not yet in the installed hook release. Registration and installation require the device owner's protected hook-release action. An old CLI may still record a build without completed_task; that is not evidence of this gate running.

#Why it exists

A build that dies in the builder queue on cargo fmt --check, on one clippy lint, or on a missing rollback declaration has spent twenty to forty minutes of a fleet builder on something checkable locally in under a minute. The shape underneath is an agent making a minimal change, submitting, waiting for the builder, reading the failure and making another minimal change — and restarting or installing units nobody asked for along the way.

A build spends a fleet builder, of which there are two for darwin-arm64, and a restart spends a service that other work is reading. So each is stated before it runs — what it is for, which revision it carries, when the authorization stops — and entries are rationed. Builds use Stado's fleet budget when available, or Tama's local rolling allowance otherwise. A user-approved exception can exceed the daily count without changing the default policy; change-size checks still apply.

#The ration

LimitWhere it comes fromWhat it stops
builds_per_dayThe policy file; without it the operator's number, 3, read from builds_per_day in numeric-provenance.jsonThe local fallback refuses another build after this count in rolling 24 hours. When Stado answers, its UTC-day fleet budget is authoritative. An explicitly approved and audited exception may exceed the count, not the other checks.
restarts_per_dayThe policy file onlyCounted separately, so a spent build allowance never holds back a service that has to come back.
minimum_changed_linesThe policy file onlyA build is measured with git against the revision the last recorded build of that target carried. Under the threshold it is refused and told to travel with the next serious change; the same revision twice is refused as a build of nothing.
window_minutesThe policy file onlyHow long one recorded intent authorizes, and the ceiling on --minutes.

The limits live in ~/.shared-hooks/build_restart_policy.json and are read with tama build policy. A limit the file does not set has no value in code: recording an intent is refused with <file> sets no <field>: the operator states it with `tama build policy <option> <n>`, which needs DEVICE_HOOK_EDIT_APPROVED=1 set outside the agent session. Tightening a limit needs nothing; setting one the file did not state, or loosening one — more builds a day, a smaller change, a longer window — needs DEVICE_HOOK_EDIT_APPROVED=1 set outside the agent session, because an agent that can raise its own allowance has none. The local rolling count comes from ~/.shared-hooks/build_restart_ledger.json. Ordinary intents are retained for thirty days; approved exceptions remain as audit evidence. Closing an entry ends the authorization and does not refund it or renew its consent. A ledger or policy that cannot be read refuses the build.

The recorder's local forecast waits until usage drops below the current limit, including when approved exceptions or a lowered limit have left usage above it. A zero limit has no timed reset. The gate does not infer spent usage from open entries, because closing an authorization does not refund an intent. The CLI and tama request builds/record share the recorder's refusal. These corrected diagnostics require the updated native release described in the build command.

One build entry authorizes one attempt against one target before expiry. The written completion statement is retained with the revision and reason; Tama requires the statement but does not independently prove that the task is finished. An agent must not record it while work remains:

tama build record --kind build --target skarbiec \
  --reason "Validate the committed identity checks in the shared build" \
  --revision 45a5c678042147f76241adf76fb7cf217e121ca6 \
  --completed-task "The whole requested identity change is finished in code and documentation"

tama build record --kind restart --target brama-desktop-model-router \
  --reason "the router holds a revoked credential and reads it once at start" \
  --minutes 30

tama build list
tama build close --kind build --target skarbiec
tama build policy
tama build policy --builds-per-day 2 --minimum-changed-lines 80
FieldWhat the command demands
--kindbuild or restart. Nothing else is an entry.
--targetThe product for a build, the service or unit for a restart. A build whose command names no target of its own — stado build submit --source ., stado release submit --source ., cargo build — is registered against the repository the command runs in.
--reasonRequired and not blank, because the entry exists to be read later by somebody who was not there.
--revisionRequired for a build, and it must be a full forty-character source revision. A build that cannot name what it carries is not recorded.
--completed-taskRequired for a build: which whole task the agent has finished before building. A missing or blank statement refuses recording and spends no allowance. Restarts do not require it.
--repositoryWhere the change is measured for a build. Without it, the repository the command runs in.
--minutesHow long the entry stays open: one to the policy's window, which is also the value when the option is absent. Past that it authorizes nothing, so an entry that is never closed cannot become a standing permission.
--approval-session, --approval-quoteFor builds only, together. The quote must match Tama's captured current user message, and Brama must verify explicit permission to exceed the daily count for the named product or batch. A request to add this mechanism is not permission to use it. The entry retains the original message, its digest, session, capture time, product, revision and verdict.

A registry file that is missing, empty or malformed holds no entries, which refuses every build and restart rather than letting them all through.

The CLI build approval --target NAME --revision FULL_SHA --json returns an unexpired entry only when its evidence also exists in the audit ledger. Stado reads it before charging an over-limit native build and stores the evidence with the run and platform in its canonical registry. The same message cannot grant another Tama intent for that product, and the same approval cannot charge another Stado run for that platform. The default count, expiry, duplicate-revision refusal, minimum change, host admission and publication permissions are unchanged.

Tama Desktop exposes the required completion statement, recording, refusals, retained approval history and closing in Policy → Build registry. A used build entry remains visible but no longer authorizes another attempt. Closing blocks subsequent approval reads; it does not cancel an already accepted job. See the build command for the complete flow and request fields. Older Tama releases do not enforce this statement.

#What is refused

Whether a command builds or restarts is read by a model through Oko's model call (block_unregistered_build_or_restart/judge-prompt.md), not from a list of programs and subcommands: every build tool, script name or service manager such a list left out built or restarted unregistered. A build compiles, bundles, packages or releases a product: a build tool's build, release or install step (cargo build, swift build, xcodebuild, stado release submit, a project's build script through its package manager), or a push of a checkout that builds on every push. A restart starts, stops, restarts, reloads or reinstalls a running service (launchctl kickstart, systemctl restart, stado service restart). The model names each build's directory and each restart's service; the build's target is the repository that directory belongs to, read from the checkout.

The refusal prints the tama build record line for that exact kind and target, so the route out of it is the command itself. A question the judge leaves unanswered refuses the command with TAMA_HOOK_INFRASTRUCTURE: block-unregistered-build-or-restart: review unavailable: …, as asking a model describes.

A push is a build when the repository builds on every push: a Vercel-linked checkout (.vercel/project.json or vercel.json), where Vercel starts a production build, or a repository carrying a CI definition that runs a build or a suite on push, pull_request or pull_request_target (under .github/workflows/, .circleci/, .buildkite/, .woodpecker/, or a .gitlab-ci.yml, azure-pipelines.yml, bitbucket-pipelines.yml or .travis.yml). Whether this checkout builds on every push is read from the checkout and handed to the model as a fact; such a push is refused until a build entry with the whole-task statement is recorded for that repository. A push from a checkout with none of these, and reads such as vercel inspect, pass.

The write gate (pre-write-edit) refuses the other half: a write or edit that adds such a build or suite step to a CI definition firing on every commit is refused with the step it found, so a build refused in the shell cannot move into a runner.

The model reads directory changes on the line (cd, git -C <path>) and names the directory a build runs in; it is resolved from the working directory the payload names (or this process's own when it names none).

#What passes

Reads of the same programs (stado build status, launchctl list), help requests, formatting, linting, type-checking and tests that produce no product are not build commands for this gate, and neither is a command that only mentions a build in data, such as grep 'cargo build' notes.md or a commit message. Other gates may still refuse hand-run compilation or tests; passing this gate is not permission to bypass the shared build process.

#Where the refusal comes from

Source: rust/crates/tama-hook-tools/src/ports/block_unregistered_build_or_restart/mod.rs, with the judged reading in detect.rs and judge-prompt.md, where a command runs and whether a push builds in directory.rs, and the registry reader in registry/; its program: rust/crates/tama-hook-tools/src/programs/block/safety/execution/block_unregistered_build_or_restart.rs, run as tama hook block-unregistered-build-or-restart; the writer: rust/crates/tama-cli/src/builds/mod.rs; the declaration the registration reads: hook-declarations/block-unregistered-build-or-restart.json. The journey cargo test --manifest-path rust/Cargo.toml -p tama-cli --test build_restart_judged runs the built hook in a real git checkout under an isolated home: with the judge recorded as not answering a build passes with the stderr note, and tama build record --kind restart --target system/com.example.unit writes its registry entry; with TAMA_TEST_BUILD_JUDGE=1 and -- --ignored it asks the real judge and requires cargo build refused naming the checkout, launchctl kickstart -k system/com.example.unit refused naming the unit, a quoted mention and a --help passed, and the restart passed once recorded. Registration is two commands: DEVICE_HOOK_EDIT_APPROVED=1 tama hooks declare --file hook-declarations/block-unregistered-build-or-restart.json for the catalogue entry, the registry entry and the reseal, and DEVICE_HOOK_EDIT_APPROVED=1 tama hooks release for the binary and the installed release.