Project
Changelog
All user-visible changes follow the categories required by releases. A published section maps to the Stado release of the same version.
All user-visible changes follow the categories required by releases. A published section maps to the Stado release of the same version.
#0.2.0 — Unreleased
#Added
- Git's
commit-msghook,tama hook review-commit, refuses a commit made in an agent session whose staged index is not only that session's recorded edits, however the commit was spelled. The shell-line commit guard saw only agittoken followed bycommit, sosh -c "git commit -a", an alias or a script committed another session's hunks unread. The hook reads the committing session from the recorded agent process among its ancestors and checks the staged index with the same receiptstama stage --sessionapplies; a commit no recorded session runs is not read. Reading the process tree one process at a time also lets consent-checked commands find their session where listing every process is refused. See session-owned staging; real journeytests/shared-checkout/hook.rs. tama format fold [--repository <path>] [--json]folds rustfmt's form into every unpublished commit of the checkout's branch: each commit between the upstream and the head is written again with the Rust files it adds or changes in rustfmt's form under their edition, keeping its message, author, committer, dates and attestation note, and later commits are carried onto it. The push gate tells an author to fold formatting into the unpublished change and the commit review refuses a commit that only reformats, but nothing performed the fold, so an unpublished range carrying one unformatted file could never be pushed. The branch moves only from the head the fold read, the upstream is never rewritten, and the index and working tree keep every change someone made there. Seetama format fold; real journeytests/format/fold.rs.block-numeric-literalsreads apackage.jsonas JSON and treats its ownversionandlicenseand the package names and release ranges of its dependency tables andenginesas the release names they are. Every edit of an npm package carrying"license": "Apache-2.0"or"engines": { "node": ">=20" }was refused for those values whatever the edit changed, so no dependency could be added to such a package. Every other number in the manifest, and the same fields in any other file, is still read; apackage.jsonthat is not JSON is refused naming the cause. See numbers; real journeytests/numeric-literals/scanning/identifiers.rs.- Every model-judged gate reads a schema-bound answer instead of the first word of a line; the prose call
model_judgement::askis gone.model_judgement::chooseasks throughoko gateway objectfor achoiceamong the options the gate names and the judge'sreason, under a strict JSON Schema;model_judgement::objectreads any object a gate's own schema describes. An answer outside the options, or one the gate's reading refuses, is an unanswered question naming the answer, and is treated as every unanswered question of that gate is.pre-bash,block-inline-execution,block-background-execution,block-one-off-repairs,block-keychain-prompt-commands,block-local-model-training,block-oko-trajectory-edits,block-subagent-spawn,block-external-source-edits,block-hook-scan-narrowing,block-device-level-tests-without-consent,check-humanized-actions,check-legacy-activation-bypass,check-merge-deletion-lossand the stop gatesblock-known-to-operator,block-number-requestsandblock-operator-commandschoose;block-unregistered-build-or-restartreads a typed list of steps,block-one-off-commandsone verdict per declared command, and the numeric reviewer a list of finding numbers. See model judgement. review-pushrefuses a push that adds a commit whose attestation stands only in itsrefs/notes/attestationnote unless the same push carries that notes ref or the remote's notes ref already names this checkout's notes commit. Git pushes no notes ref unless asked, so commits attested by notes reached remotes with no attestation at all while the notes stayed in one checkout. The refusal names both notes commits and the push that carries the note;tama attestation pendingreports the same undernotesand Tama Desktop's unpushed-commits section shows it. Seetama attestation; real journeytests/review/push/noted.rs.block-tools-in-purgatory: a session whose answer the operator gave his worst verdict may not call any tool until it writes a self-criticism; the refusal quotes his verdicts and admits onlyoko purgatory statusandoko purgatory confessfor that very session. See finishing work; real journeytests/purgatory/gate.rs.- The compile gate of
block-one-off-repairskeeps no count of open work: it asks Oko's one admission decision,oko admit build --session <id>, which answers from the operator's rulings and, where none decides, from his rule that a session's build waits until every open item carries its pushed repair. A standing withdrawal refuses quoting the ruling; a standing consent admits; a build the rule holds is refused with Oko's reason unless the build-consent answer or the session's build capability admits it; an installed Oko that cannot be read admits nothing. The per-session recorded reading of open work and the repository-history ordering of repairs are gone, and the stop gate holds for the build exactly the itemsoko blockers heldlists. See how the build gate decides a build; real journeystests/build-gate/consent.rsandtests/build-gate/standing.rs. block-unclaimed-calls: a tool call runs only for a session that claims an open Oko item and only with its purpose stated in the call's intent; each admitted call is kept with its items incalls.<session>.jsonl. Oko's own commands are never refused for want of a claim.block-supervisor-stoplists the supervisor's unanswered review entries with the command that answers each. See finishing work; real journeytests/unclaimed-calls/gate.rs.- The open-work stop refusal states the open totals and names the open set by its digest (
open set <sha256>; one product's: `oko defects list --open --product NAME`), with the session's own items and the one next item, and lists neither every open identity nor a count per product: the per-product line alone ran to a hundred and fifty entries on every refused stop. Two stops over an unchanged set give the same text, so the core's short form holds until an item opens or closes. A defect register or task ledger the stop cannot read now refuses the stop with the cause instead of falling through to the slower whole reading. See finishing work; real journeytests/claimed-work/stop.rs. block-stop-with-open-worktreats a claimed repair as open work. Once every open item carries a pushed repair, the refusal names the next repair claimed on its author's word and not verified, as Oko's work queue serves it after every unrepaired item (oko work next --executor SESSION [--product P]), with the verification it needs, instead of offering the batch build: thousands of items carried only their author's claim while sessions were told nothing was left. See finishing work; real journeytests/claimed-work/stop.rs.- The core lets a stop through when the gates together leave the session no step: the stop is refused again by the gate that refused the previous stop (the same refusal or the same
blockedHookId), the operator wrote nothing since, and no call the session attempted since ran — each was refused before it ran, or the session only answered again. The decision names the cycle (TAMA GATE CYCLE: …), the stop refusal and each refused call, and the turn-end record keeps them undercyclebesidetranscriptBytes. A session that made a call that ran, or a stop after an operator message, stays refused. See conduct gates; real journeytests/gate-cycle/release.rs. block-wait-pollingrefuses a look the session already made and got answered while nothing it did since could change the answer — no file change, no commit, no message from a person — quoting that answer, with no count and no window; the same look passes again after any of those.block-idle-answercounts work after a stop refusal by the same ledger, so a repeated look no longer buys an ending. In a session Oko records as a supervisor, executor transcripts, Oko's data andokocommand answers are read whole: a pipeline stage fed by one, another program handed one, or a read with a selector or query on one is refused, namingoko sessions messagesandoko defects show. See finishing work; real testtests/repeated-looks/gate.rs.tama sessions revoke-capabilityis now the grouptama sessions capability show|revoke --session ID --agent omp|claude|codex: a verb joined to its object stood beside the session verbs, and the grant could be withdrawn but not read.showprints the capability the session's override holds as the build gate reads it.--agentis required: the command read an unnamed harness as OMP, so a Claude Code or Codex session's grant was looked up in another control file. See capability.block-unapproved-testsrefuses anyokocommand a session runs with--by operator, not onlyoko tasks verdict: a relation (oko tasks relate … --by operator) or a message's reading was signed as the operator's own decision when a session wrote it. The same command signed by the session passes. See tests; real journeytests/unapproved-tests/operator_acts.rs.review-pushrecords each commit it lets through in Oko's claim register: apushclaim on<repository>@<commit>with its structural verdict, which holds, so Oko's registers and Oko Desktop read the push review's verdict instead of a record Tama keeps. A refused push records nothing; a claim that cannot be recorded is said on stderr and does not change the verdict. Seetama attestation; real journeytests/review/push/claimed.rs.block-keyword-literalsreads a command line built a step at a time as the process's arguments: a list bound to a name that a later step hands an option (var args = ["crawl", "start"], thenargs.append("--catalog"), orargs += ["--json"]) is that command line, so a file building a CLI's arguments from literal verbs can be edited again. A bound list grown only with values is still refused. See hook model; real journeytests/keyword-literals/argv/built.rs.block-keyword-literalsreads a list a function returns as that function's value, as it reads a list handed to a call: a view whose table headers or card labels stand in the object areturnstatement builds was refused as a kept vocabulary, so a screen that renders a table could not be edited. A returned list asked a question where it stands (.includes(…),.contains(…)) and a list bound to a name are still refused. See hook model; real journeytests/keyword-literals/returned_views.rs.block-numeric-literalsjudges every literal the file holds as the write leaves it again, as numeric values describes: comparing the file with the file as it was and refusing only the literals an edit added let a number nobody sourced stay in the file on every later edit, against the operator's rule that a violation a file holds is cleaned whoever left it. A literal is named on the line it stands on in the source; counted in the text with strings and comments blanked, the line drifted after every multi-line comment or string and named a line the literal is not on. See whole-file judging; real journeystests/numeric-literals/existing_file.rsandtests/numeric-literals/replay/patch.rs.block-fallbacksrecognises a fallback in the file's syntax tree, in every language it judges, instead of matching lines of text: each construct is a tree-sitter query inconstructs.json, declared once per language family with the grammar each file extension is parsed by (Rust, JavaScript, TypeScript and TSX, Python, Swift, shell). It now also refuses what drops the error that says why a value failed — Rust.ok(),Result::ok,map_orand anErr(_)arm; a JavaScript or TypeScriptcatchthat binds no error, is empty or answers a literal, and.catch(() => literal); a Pythonexceptthat only passes or continues or returns a literal,getattrwith a default andsuppress; an empty Swiftcatch; shell|| true— and a construct named in a string or a comment is no longer refused. The refusal quotes the operator's rule; a file whose grammar or construct cannot be read is refused with that cause.tama violations find --fallbacks(and the Desktop's Fallbacks and dropped errors pass) lists every fallback each repository still holds, per repository. See no fallbacks; real journeystests/fallbacks/languages.rsandtests/violations/fallbacks.rs.- The standing-rules block writes one instruction said in reworded variants once. The operator repeats a rule when a session forgets it, each later saying inserting words into the earlier one's sentences, and the block after every compaction carried every variant in full, about a third of a fresh context. A text every word of which stands, in order, in a fuller text is one rule with it: the fullest text is written once, under its newest message, with
said N times since <time>counting every variant, and a ruling whose words a standing rule carries is listed aswords a standing rule above carries in fullinstead of copied. No word of any variant is cut. See enforcement control; real journeytests/operator-rules/carried.rsagainst a ledger fixture. block-crowded-foldersjudges every file a hashline edit names. A script editing two existing files in one call reached the gate as the call's own path, which the harness fills with the session folder when the script names its files itself, and was refused as a new file in that folder; the gate now reads the script's section headers and judges each file on its own, anapply_patchenvelope by the files it leaves, a plain write by its one path, and refuses a section creating a file in a crowded folder naming that folder. See hook management; real journeytests/crowded-folders/hashline.rs.block-keyword-literalstells a data document's references to itself from a vocabulary by the document, not by a key name: a list in a JSON or TOML file whose every word is a key the same document declares passes, and a list naming anything else is judged. A Cargo[features]table passed whole and a JSON Schema'srequiredpassed only beside its ownproperties, so each new misfire was answered with one more exempt key; nowdefault = ['signal', 'process']passes when the manifest declares both features, anddefault = ['accepted', 'rejected']naming nothing it declares is refused. Cargo's array keys (a dependency'sfeatures,members,include,keywordsand the rest) stay as Cargo's manifest schema. Real journeytests/keyword-literals/cargo_manifest.rs.- The open-work completion refusal counts instead of copying.
block-stop-with-open-workandcheck-open-items-with-modelsay how many open tasks and defects Oko holds and how many carry no pushed repair, list only the session's own tasks and the defects Oko records it as owning, name the one item to take now and the commands that print the rest (oko tasks owed,oko defects list --unrepaired,oko defects list --open --owner SESSION_ID); the operator's answers to excuses are no longer appended, since they stand in the session's context from its start and every compaction. A session's own plan is refused with its count and first item.tama assignment showprints the defect counts and the session's owned defects instead of every open defect with its plan, and no longer repeats the standing rules. The standing-rules block writes one line per ruling message with every subject it decides, and a ruling in a standing rule's words points at that rule instead of copying it. Each refused turn had carried the whole register, and the session read it again on every later call. See finishing work and assignment show; real journeystests/completion/summary.rs,tests/stop-gate/repeat.rsandtests/assignment/show_registers.rs. - The build gate reads the operator's standing build consent through one reader before it opens any dialog: a ruling Oko records on
buildorbuild:<product>admits the build without asking the session window, andtama attestation list --kind buildlists those standing consents (storeoko rulings, lifetime until withdrawn). Swift package builds (swift build|test|run,stado product swift) are named by their checkout. A consent given in a conversation for SSH, a local browser or a hook edit stays a session capability. See finishing work; real journeytests/build-gate/standing.rs. - A release install names Tama an announcer in Oko (
oko asks announcers add tama), so the grant window's announcement throughoko asks askis recorded and delivered on its own and the grant is applied as soon as the window answers, never held on Oko's channels.installed.jsoncarriesokoAnnouncer: named, or the command and its cause, also on stderr; a machine without Oko keeps the install. See hook releases; the native install journeytests/harnesses/install.rschecks the field. block-identity-literalsreads each source once per written file: the values no cheaper source carries are looked up in the stored sessions in one walk that reads each transcript's index once and ends when every value has two sessions, a path outside any checkout is not searched, and agit grepthat cannot start, ends on a signal or writes an error is named, with git's own error, in every refusal of the written file instead of read as no match. An artifact folder or file it cannot read is named on stderr. See identity provenance; real journeystests/identity-literals/tracked_ids.rsandtests/identity-literals/mentions.rs.- Every registry path the gates read has one definition: the hook folder is named once, and the file, CUA and SSH justification registries are defined by the gate that reads each (the variable that moves a registry included).
tama justify,tama attestation,require-new-file-justification(whose refusal names the registry it read), the frustration detector andcheck-numeric-literalsresolve through them.check-numeric-literalsrecognizes the SSH grant registry by the SSH gate's own path, so a registry moved byTAMA_SSH_JUSTIFICATIONis read as grant text and a file of the same name elsewhere is judged as configuration, and takes grant text out by its shape (a string, or a list of strings) instead of a list of field names. Real journeytests/numeric-literals/consent.rs. - Every attestation is one record in one ledger. Session records, file, CUA and SSH justifications and test approvals are written by their verbs through one primitive into
attestations/ledger.jsonunder the Tama state root, each naming the registry the gate reads, which stays where the hook catalogue declares it as a view.tama attestation migrate(andattestation/migrate, the Desktop's Migrate) brings what the registries hold into the ledger, and a release install migrates on its own;listreads the ledger and refuses until it is written;verifynames each record a registry and the ledger disagree on. See tama attestation. - The rules block
inject-operator-rulesputs in front of a session at its start and after every compaction writes each of the operator's sentences once. He rewords a standing rule by inserting words into some of its sentences, so one instruction stood in the block five times as standing rules and again as rulings, about 30,000 tokens re-sent after every compaction; now a variant or a ruling keeps only its sentences not yet written and says how many stand above. Sentences are compared whole, so a sentence that differs by one word (a nie included) is written and no order is merged into another. See tama assignment show; real journeytests/operator-rules/distinct.rs. pre-bashrefuses a shell command that hides what it says. A redirection into the null device by the shell's own syntax (>/dev/null,2>/dev/null,&>/dev/nulland the rest) is refused naming the construct; a filter that keeps only part of the producing command's output (first or last lines, a column range, a count instead of the lines) is the judge's new shapeTRUNCATE, refused by what the command does and not by the filter's name. A failed or hung step read as quiet success when its errors went into the null device or its output was cut on the way. See shell commands; real journeytests/hidden-output/null_sink.rs.pre-bashrefuses a Git command that would discard another session's uncommitted work in the one shared checkout.git restore <path>,git reset --hardandgit cleanare read against the session's own transcript, which says which files it wrote; every other uncommitted change the tree holds (Git's owndiff --name-only HEADand untracked listing) is another session's or the operator's, and a command that would discard one is refused naming the files (BLOCKED: `git restore src/lib.rs` would discard uncommitted changes this session did not make in <checkout>: src/lib.rs. …). A restore or areset --hardis judged against the tracked changes and untracked files under its paths, acleanagainst the untracked files alone, andgit -C <dir>names the checkout judged. Discarding only one's own files and unstaging pass; a discard whose ownership cannot be read (no readable transcript, a checkout Git cannot list) is refused naming the cause. See shell commands; real journeytests/shared-checkout/discard.rs.tama stage --sessionand Reviews → Session-owned staging apply successful recorded edit deltas to the Git index without taking another session's hunks from the same file. Inspection shows the candidate tree without publishing it; overlap, stale content and missing receipts refuse atomically. The commit guard shares the same receipt reader and compares exact staged content instead of granting ownership of an entire touched path. Whole-path and compound-command commits refuse; no compilation is introduced. The recorded-session journey checks exact tree identity, repeated staging, conflict refusal and unchanged operator files and index.- Brama's fleet route is read first from the adapter this machine's resolver publishes as listening for Tama's consumer (
~/.stado/resolver-state.json, orSTADO_RESOLVER_STATE_FILE), and only then from astado service directory connectchild: a child that stood for hours on a channel the object API never answered held every hook judge on the machine behind it while the published adapter was listening all along. The state counts only while the process that published it still runs (ps -p): the file outlives the resolver that wrote it. An adapter published as not listening, or by a process that is gone, is not a route; the search goes on to Stado as before. See model provider; real journeytests/brama-route/published.rs. - A fourth managed Git dispatcher,
reference-transaction, runstama hook review-ref-moveon every move of a local branch. A rewrite or reset (git rebase,filter-branch,commit --amend,reset --hard) is read from Git's own old and new tips; every unpushed commit it drops is looked up in the defect register (oko defects naming <repository>@<commit>), and a commit an open defect records as its found-in or repair whose patch no commit under the new tip carries refuses the move before Git lets the history go (review-ref-move: BLOCKED: refs/heads/<branch>: the move drops <sha>, which the register names …). A rewrite that keeps every named patch passes, and once the refs have movedoko defects rebased --applymoves each entry to the new id. A recorded repair used to vanish from main silently when an unpushed history was rewritten, the register still naming an id no branch carried. A machine without Oko has no register to ask and says so. See tama install; real journeytests/review/ref_move.rs. review-pushand the turn-end stop gatescheck-stop-asking,block-false-blockersandblock-stop-on-own-workask no model: a pushed commit carries the commit standard's claims in its own message and an ending answer carries the claims each gate declares instandards/turn.json, read word for word, and a text that lacks one is refused naming every missing claim (review-push: BLOCKED: … commit <sha> carries no commit attestation in its message:/missing: "<claim>"). No key, Brama or other product is involved, so a push and a turn end are judged the same on a machine with nothing but Git and Tama.tama attestation standard --commit|--turnprints what to carry andtama attestation check --commit REV|--turn HOOK --file PATHgives the gate's verdict; Tama Desktop's Carried attestations section does the same throughattestation/requiredandattestation/check. The recorded commit attestation (tama attestation record --commit), the pre-bash commit gate that read it, the push model review with its grounding and retained exchanges, andtama hook review-routeare removed. Seetama attestation; real journeystests/review/attested.rsandtests/task-attestation/turn.rs.block-unlinked-operator-messagesis a stop gate that refuses the end of a turn while any operator message of the session is linked to no Oko record: it runsoko rulings unlinked --session <ID>, which names for each message the ruling recorded from it or the defect its task is related to, and relays Oko's refusal with the two commands that link an unlinked one. A correction the operator gives is open work in the registers from that turn on, not words he teaches again in the next session. No model decides it; a machine without the Oko CLI is left alone. See finishing work; real journeytests/operator-rules/unlinked.rs.- Tama's push gate and name gate run on a machine without Stado.
tama hook quality-check, with no~/.stado/bin/stadoinstalled, reads the checkout's.wisent-release.jsonitself and runs each gate it declares for this platform in the checkout, the first that does not pass refusing the push by its name (quality-check: the declared gate <name> refused this push of <checkout> …); a checkout declaring none passes, a manifest that cannot be read refuses by its cause.block-internal-namessays that the fleet registry and the vault listing are not here and judges this account's own names, instead of refusing every write becausestado credentials lscannot run; a Stado that is installed and cannot answer stays unread. The catalog declares Tama's dependency on Stado with this alternative, sostado product dependenciesreads it. Real journeystests/review/quality/declared.rsandtests/internal-names/without_stado.rs. - The open-work stop gate runs on a machine without Oko. With no Oko CLI installed,
block-stop-with-open-workreads the session's own task-scope declaration (tama task scope set … --task | --defect, the entryrequire-task-scopemakes it) as the register: a declaration that still names a task or a defect refuses the stop naming the entry and the operator's words (BLOCKED: this machine has no Oko CLI, and the session's own declaration still names open work: …) and the way out,tama task scope clear --session <ID>; a cleared declaration lets the stop through, and one that cannot be read refuses as an unreadable ledger. The catalog declares Tama's dependency on Oko with this alternative. See finishing work; real journeytests/stop-gate/without_oko.rs. - A judge request waits only while Brama keeps declaring it serves. While the hook's client waits on an accepted request, Tama asks Brama's readiness (
GET /readyz) again on the same adapter, each answer pacing the next question; a Brama that stops declaring it serves, or an adapter that stops answering readiness, ends the client and the request is refused asthe judge request to … was ended while it waited: Brama no longer declares it serves (…)with Brama's declaration. One request forwarded to a Brama that never answered held the resolver adapter for hours, and every later judge on the machine was refused behind it; no time limit is guessed. See adapters; real journeytests/hooks/readiness_watch.rs. - Every gate built on the write replay judges an
apply_patchenvelope and a notebook cell edit by the file they leave. The replay rebuilds each added and updated file of an envelope (*** Add File,*** Update Filewith its@@hunks and*** Move to,*** Delete Fileleaving nothing) and a notebook as its cell edit leaves it (replace,insertafter the named cell or at the head,delete, bycell_idorcell_number); a hunk the file does not hold, a cell the notebook lacks, a notebook that is not JSON or an unknownedit_moderefuses the write naming it, and nothing is judged by its fragment. Before, an envelope was refused as unreplayable and a notebook edit as having no supported operation. Real journeystests/numeric-literals/replay/patch.rsandtests/numeric-literals/reconstruction.rs. block-hidden-subcommandsreads the hyphen clap derives without one being spelled: a variant of aSubcommandenum whose CamelCase name has more than one word and carries nonameattribute (GpuPowerLimit, which clap serves asgpu-power-limit) is refused asclap subcommand whose CamelCase variant derives a hyphen-joined name (no name attribute)with its line. A dozen host-side verbs passed the gate this way while spelling none of their hyphens. A variant whose attribute names it, a one-word variant, an acronym and an enum that is not aSubcommandpass. Real journeytests/hidden-subcommands/refusal.rs.block-unrepaired-negative-reportis a stop gate for a session whose Oko role issupervisor: Brama's reviewer names every sentence of the ending answer that reports something broken, stopped, refused or missing and that no action of the turn answers, with the Oko defect it cites, and Oko's register (oko defects show <id> --json) decides whether that defect has a recorded repair or an owner. A sentence citing no such defect refuses the stop, quoted with its cause. See supervisor negative reports; real journeytests/supervisor-stop/negative_report.rs.- Tama's model calls no longer carry a request over a system-SSH forward to the host a stale registry copy names (
~/.stado/cache/registry-last-good.json) when this host's Stado cannot answer. That route reached hosts outside Stado and answered from a copy of any age; a call is now refused naming the fleet route's and the relay's own causes. - The standing-rules block that
inject-operator-rulesandtama assignment showprint gives each distinct rule text once, under the newest message carrying it, withsaid N times since <time>when the operator said it in several sessions. Malformed ledger reports carry their decoding cause instead of becoming an empty rules block or invented message provenance. The live-ledger journeytests/operator-rules/distinct.rschecks every distinct text and retains the source revision, working-tree diff, commands, exits and raw outputs; a ledger changed during the journey is reported as an inconclusive run. - OMP sessions receive the operator's standing rules after every compaction: the OMP adapter puts the context of the
session_start:compacthooks (inject-operator-rules) into the session as a displayed message, or the reason the hooks refused or failed. Before, the adapter ran the hooks and dropped what they returned, so no OMP session got the rules back. A passinguser_prompt_submithook's context is delivered the same way. tama hardcodes, itshardcodes/scanrequest and Tama Desktop's Hardcodes screen are removed. The command reported every number, address and name in a tree as one undifferentiated count, which is no operation on an object Tama owns and no step toward giving a value its source; a value that decides behaviour is refused where it is written, byblock-numeric-literalsand its citation rule.tama sessions remind (--session ID | --default) [--text SENTENCE | --clear]sets the sentence theremind-open-workhook puts after every tool result: one session's own sentence always, the machine-wide one while Oko holds open work. The hook's sentence was compiled in; it now comes only fromsession-control/reminders.json, so set the machine-wide sentence with--defaultafter installing this release.- Tama computes SHA-256 with the
sha2library everywhere; the two hand-written copies of the algorithm and their constant tables are gone. - The local build ration's builds a day, when the policy file does not set it, is the operator's stated number read from
numeric-provenance.jsoninstead of a constant. The adaptive telemetry corpus stores each hook payload whole: a payload over 65,536 UTF-16 units used to be stored as a cut JSON string that no candidate repair could replay. A Brama route named inTAMA_OBJECTIVE_AUTHORITY_MODEL_ROUTEis no longer refused for being over 160 characters; only whitespace makes it unusable, and whether Brama serves it is Brama's answer. block-wait-pollingrefuses a shell call that blocks until the operator answers (oko asks wait, declared inblock_wait_polling/operator_waits.json), with his rule that waiting for his decision is forbidden: an agent that ran one stood still for as long as he did not answer. The ask stays open and delivered;oko asks showreads its answer when it comes. Asking, reading and listing asks pass.tama find-violationsreads every file whole: a file over 524,288 bytes is no longer skipped aslarger than 524288 bytes, and a file counts as binary when it holds a NUL byte anywhere rather than in its first 8,192 bytes. The rule-gaming check reads every changed file whatever its size.- Hooks decide with no number nobody stated.
block-wait-pollingrefuses only a shell call that does nothing but pass time; the third identical hub look in two minutes is no longer counted.block-repeated-blockeraccepts a blocker's still-blocks check made since the operator's latest message, read from the transcript, instead of within an hour. Session records carry liveness as the owner process, the heartbeat the launcher states inTAMA_SESSION_HEARTBEAT_TTL_SECONDS, orunknownwithlivenessError; the 900-second default and the 5–3600-second clamp are gone, and Tama Desktop shows the reason.block-internal-namesreads a prose part as a sentence when plain words are most of its tokens, not at five words. Completion refusals name every open task and defect by identifier and quote none of their text, with no 140-character clip or 12-item cut. The session-start context carries every standing rule and ruling, with no 7,000/2,500-character split. tama sessions revoke-capability --session IDwithdraws the capability granted to one session — a build consent the operator takes back — at once instead of at its expiry: the override'scapabilitybecomesnull, the build gate refuses the next build, and the running session reports"capability": null. Before, a capability could only be granted.- CUA consent recording, listing and removal through
tama justify --kind cua,tama requestand Justifications → CUA consent. Grants are taken from actual session user messages and validated against installed application metadata without manual protected-file edits. - Product contract, onboarding, operational, core, integration, example, testing, release, security, and support documentation.
- Authenticated guided setup that records completion only after the sealed runtime, enabled privileged backend, and a matching live kernel-gated agent session are all observed.
- Desktop build provenance and immutable release packaging contract.
- Public Tama CLI for catalog listing and validation with opt-in local install-drift checks, exact hook inspection, live session state, registry-declared provider coverage, Git dispatcher installation, repository scans and cleanup, adaptive recovery, MCP configuration, and
tama request, the per-operation entry point the desktop runs. - Runnable
examples/shell commands with inline risk, required-input, side-effect, and rollback comments; CLI link installation is retry-safe, and prose runbooks plus the Markdown coverage matrix were removed. - Session destination that renders the capability a supervised session holds — lifetime, expiry, remaining uses and every tool grant — together with the register of semantic decisions that session published.
- Coverage destination for registry-declared provider coverage, and an Install plan destination that states each scope's target paths, the exact commands that write them, and the MCP server snippet.
- Settings destination for local enforcement — runtime installation, privileged backend registration and macOS approvals, and the confirmed deactivation of everything — plus the product and hook-release build identities.
- Documentation corpus: per-screen desktop reference under
docs/desktop/, concept pages underdocs/concepts/, two executed walkthroughs (release seal verification, runtime status), a runbook of exact refusal sentences, a scripts reference, and runnable read-only integrity examples underexamples/integrity/.
#Changed
- The compile gate's refusal no longer sends the agent to the operator for a build: it named
tama build consent askand the session capability{"tool": "build"}as ways to get one, so agents put build requests in his window and his Oko channels whenever a build was held. It now tells the agent never to ask him for a build, neither through his window nor through an Oko ask; his words in the conversation are read into his rulings, and a build no ruling admits waits, as it does while the installed Oko cannot answer the admission. See one-off repairs; real journeytests/build-gate/consent_ask.rs. - Questions with a fixed set of answers are asked as typed decisions on Brama's
POST /v1/decisionsinstead of as chat: whether a text says something (check-failure-inspection-claim,pre-edit-trajectory-evidence,block-unsearched-blocker-claim, the smoke-test gate), which candidate tests a listing names as smoke tests, whether a prompt is frustration with the assistant, and whether a prompt disputes a block. Each is achoicebetweenyesandno(one per candidate, in one call), and the hook reads the side Brama chose. The prompts that demandedYES,NO,NONEor a comma-separated list, and the parsers that refused anything else, are gone.bestasksbest-decision-model; a service declared withtama model-provider set --originanswers chat only and is not sent these questions. See typed questions. - Every gate that judges a shell command or a write by model refuses the call when its question goes unanswered:
TAMA_HOOK_INFRASTRUCTURE: <hook>: review unavailable: <cause>, the cause naming the judge's recorded state, the held question, the missing Oko or the gateway's failure. Each used to let the call through with only a stderr line, so while Brama or Oko's model call did not answer, throwaway scripts, inline programs, hand repairs, unregistered builds and restarts, keychain prompts, background work, local training, headless agents, external source edits, test edits, browser automation in Oko and truncated output were all allowed for every session. A line whose every command runs Tama, Oko or a product Stado's catalog declares Oko depending on still passes, saying so, so the judge can be repaired; so does any call on a machine that declared no model. The refusal of a keychain prompt, external source edit, test edit, local training run, headless agent or Oko boundary write no longer wraps an unanswered question in that gate's own accusation. The operator approvalsDEVICE_SUBAGENT_SPAWN_APPROVED,LOCAL_MODEL_TRAINING_APPROVEDandMERGE_DELETION_LOSS_ACKare documented on hook management. Real journeys:tests/model-judgement/judge_repairs.rsand each gate's judge-down case. - The judge channel gate tells which waiting connection is a running hook's request by the connection, not by how many there are. It compared the count of judge requests running hooks recorded with the count of connections the resolver listed, so a running hook's own request the resolver did not list let a connection no hook watches through. Where the resolver records the client address of a held connection, Tama asks
lsofwhich processes hold that loopback port, and the connection is a running hook's only when one of them is the client process that hook recorded; connections listed without a client address are still known only by count. Real journey:tests/hooks/judge_channel.rs(a_waiting_connection_is_known_by_its_client_not_counted). - A judge request a running hook owns no longer holds back every other hook's judge on the host. The channel gate refused a new request whenever the resolver listed any connection on the Brama adapter, so while one model answer was being written every other judged hook of every session answered
review unavailable … another request is not sent while that channel is waiting, and judging ran one request at a time. A connection a running hook owns is watched against Brama's readiness and ended if Brama stops serving, so it is no longer a reason to refuse: the new request goes out after Brama's own/readyz. A waiting connection no running hook owns still refuses, withrunning Tama hooks own N request(s) (…); M of the connections is no running hook's request: …orno running Tama hook owns any of them. Real journeytests/hooks/judge_channel.rs. - A judge request's body travels on a file curl reads by path (
~/.tama/judge-requests/, owned by the hook process and removed when the request ends), and the config document on curl's standard input keeps only the endpoint and headers. The body used to be onedata-binaryline of that document, and curl reads a config line only up to its own fixed length: a stop hook's prompt carrying the turn's transcript outgrew it, curl answeredcannot read config from -(exit 26), and every judged hook reportedreview unavailablewith nothing naming the line, indistinguishable from Brama's own refusals. A config document curl still refuses is named line by line, each by its option and length in bytes, with the body file and its size; the values, the bearer among them, are never printed. Real journeytests/judge-request/body.rsagainst the real Brama endpoint. - Every gate reads a tool call through one reader,
tool_vocabulary::Call: the tool's name undertool_name,toolornameor nested astool: {name, input}, compared without regard to case; its arguments undertool_inputorinput; the file underfile_path,pathornotebook_path; the shell line undercommandorcmd; and what a call runs (a shell line, an evaluation program, a started process). Each gate used to read the payload itself, and the ones written for Claude Code's spelling (Bash,Read,file_path) let the same OMP call through. The journeytests/payload-shapes/every_gate.rsruns every registeredpre_tool_usegate on the same calls in both harnesses' shapes and requires the same decision.block-subagent-spawnno longer keeps a list of agent program names: a command handing a program a headless argument (-p,--prompt,exec, …) is read by a model, which chooses whether it starts a coding agent; a question it leaves unanswered refuses the command with its cause. block-overleaf-apijudges a write by each file as the write leaves it — a hashline section, a string edit, anapply_patchenvelope or a notebook cell edit replayed on the file, a written file's content — instead of by every string of the tool input: an Overleaf history call the file already holds is refused on its next edit, text being removed is not read as written, and the refusal names the file and the line of each match (matched: <reason> (line N: …)). A write the replay cannot rebuild — a file that does not exist, an edit whose old text is absent — is refused withthe file this write leaves cannot be rebuilt to judge it: <cause>, never judged by its fragment. A call that names no tool is still judged by everything it carries. Real journeytests/boundaries/overleaf.rs(a_write_is_judged_by_the_file_it_leaves).block-direct-lsi-api,check-raw-api-calls,block-openai-api-keyandcheck-humanized-actionsjudge a write only by the file it leaves. Each used to fall back, when the replay could not rebuild the file, to the text a patch or a notebook cell writes — so a direct API call, a raw site call or a credential another cell of the notebook or the rest of the file already held passed the edit. Now a write whose file cannot be rebuilt is refused withthe file this write leaves cannot be rebuilt to judge it: <cause>, the cause naming the file, and a notebook cell edit is judged by the notebook as the edit leaves it. Real journeys:tests/lsi-api/edits.rs,tests/raw-api-calls/edits.rs,tests/openai-api-key/writes.rsandtests/humanized-actions/edits.rs.- The pre-push dispatcher runs a product's declared quality gate through
tama hook quality-check(stado quality check --if-declaredin the checkout) and refuses in Stado's own terms. It used to printrun stado quality format there, commit the result and push againwhatever Stado refused, so a check that failed because a release input could not be read (Skarbiec unreachable) sent the pusher to reformat a tree whose formatting had passed. Now a formatting gate's refusal — the one Stado marks with`stado quality format` writes what it reads— prescribesstado quality format; any other refusal repeats Stado's failure line asquality-check: stado quality check refused this push of <checkout> at <failure_point> [<error_code>]: <detail>; this is not a formatting finding, so `stado quality format` changes nothing for it; repair that operation and push again; a refusal without a failure line says so. Everything Stado printed stays above the sentence. Real journeytests/quality-check/refusal.rs: an unformatted committed tree, a committed Cargo.lock that does not resolve its manifest, and a tree the check passes. - Every write gate names a refusal by its line in the whole file the write leaves (
line N: …, numbered as an editor numbers lines), from one shared locator:check-legacy-activation-bypass,block-direct-lsi-api,block-openai-api-key,check-reward-hackingandblock-unapproved-testsused to name only the file, or joined every file a patch leaves into one text;check-humanized-actionsnow shows its model each line with its number in the file.block-unapproved-testsexempts atest/directory inside a Next.jsapp/tree by that directory, not by a list of file stems. See Writes are judged by the whole file they leave. Real journeytests/hooks/whole_file.rs. - A judge request belongs to the hook process that sent it. One request Brama never answered held the resolver adapter, and every other hook's judge was refused with
another request is not sent while that channel is waitinguntil the process that sent it was killed, with nothing saying whose request it was. Each judge request now records itself under~/.tama/judge-channels/while it waits; the refusal names the route, the hook process and its command line and the client process; a request whose hook ended has its client ended by the next judge, so the channel goes with its hook. Before sending, Tama asks Brama's own/readyz, and a Brama that declares it serves no traffic refuses the request with its declared reason instead of leaving it unanswered. See Adapters. Real journeytests/hooks/judge_channel.rs. check-visual-before-logschecks OMP's reads: it compared the tool name withReadand took the file only fromfile_path, while OMP sendsreadwithpath, so no OMP read of a run log was ever held for the visual summary. Real journeytests/visual-before-logs/read_tool.rs.check-frame-density,check-animation-verdict,check-artifact-inspection,check-failure-inspectionandinspect-required-checkcount the reads an OMP transcript records (toolCallblocks ofreadwitharguments.path), not only Claude Code'stool_useReadblocks, so an OMP session that read the frames or the recording is no longer told it read none. The journeytests/artifact-inspection/locations.rsruns an OMP transcript.block-git-hook-bypass,check-git-hooks-path,block-keychain-prompt-commands,block-overleaf-api,block-direct-lsi-apiandcheck-legacy-activation-bypassread a shell call in every spelling a harness gives the shell tool — Claude'sBash, OMP'sbash,functions.bash, Codex'sexec_command— from one declaration, case-blind. Each kept its own case-sensitive list holdingBash, so every OMP shell call passed them unread; their edit checks likewise read every write tool spelling. Real journeytests/shell-tool-names/spellings.rs.check-legacy-activation-bypassno longer lets a shell command through because its first word is on a list of reading programs: a command that carries a marker of the retired upload path is read by a model, which chooses whether it only reads or searches for the text or runs, writes or configures the path. A question it leaves unanswered refuses the command with its cause.check-merge-deletion-lossjudges every tracked file of a pushed merge, not only prose files picked by extension: arithmetic names a candidate, and a model reads the cut and the re-added diff and chooses whether the merge quietly undid a deliberate condensation or carried the other side's own work. The refusal carries the judge's reason; a question it leaves unanswered refuses the push with its cause.tama vocabulary reportcounts word lists written as named constants: words bound one by one to names in a file and used together as one class — a collection of the names (&[WRITE_TOOL, EDIT_TOOL]), the arms of a pattern (matches!(tool, WRITE_TOOL | EDIT_TOOL)) or a comparison chain with them — reported with the words the names hold. Moving a list's words into names made the count fall while the classifier stayed.block-keyword-literalsdoes not refuse this shape. Real journeytests/vocabulary-report/named_constants.rs; seetama vocabulary report.- Tama is one program. Every native hook runs as
tama hook <name>, the hook engine astama run-hook, the MCP server astama mcpand the test runners astama testkit <name>; notama-<hook>,tama-run-hookortama-mcp-serverbinary is built.tama hooks releaseinstalls onlytama, removes the separate programs an earlier release installed, and refuses a registry or adapter that still names one;tama hook migrate-registry-to-rustrewrites the registry. - The CLI is built, bundled and installed as
tama. The earliertama-cliname is gone:tama hooks releaseremoves the old~/.local/bin/tama-clicopy, and the sealed release carriesbin/tama. - Every backend operation runs as one
tama request <operation>process that exits once it has answered, instead of a residenttama serveloopback server kept alive for the whole session; cancelling a scan or cleanup, or quitting the app, ends that operation's process tree. - The file/test justification recorder uses the shared
justifications/recordoperation instead of spawning a separate CLI command from the native view. - Runtime installation and macOS policy registration require explicit authenticated confirmation instead of running during model initialization.
- Session discovery is read-only when Tama has not been configured.
- Session monitoring starts only while the authenticated control surface is visible.
- Policy controls now require a current recognized Wisent organization role, and mutation-capable models enforce an explicit construction-time authorization boundary.
- Tama opens Wisent session restoration directly; the explicitly selected read-only inspector uses isolated bundled-catalog state, while incomplete authenticated setup resumes until its evidence is complete.
- In-flight repository scans and cleanup can be stopped; scan cancellation is read-only, while cleanup preserves partial edits and performs the final rescan.
- Runtime-drift CLI checks now resolve Claude and Codex adapter configuration from the current or explicitly selected user home instead of maintainer paths embedded in the signed catalog.
- Local setup and emergency commands now drain output concurrently, enforce bounded output and runtime, and end their process tree when that bound is passed.
- Leaving the authenticated control surface cancels active repository scan or cleanup process trees and requires inspection plus a new read-only scan after partial cleanup.
- Product and hook-release identities are displayed separately.
- OMP session-control reload operations await the common native hook engine and expose its observed generation or exact failure, without inserting a prompt or restarting the agent. A legacy adapter needs one command-handler reload before it can use this path.
- Per-session controls now expose only policy-permitted enable operations and wait for the live supervisor response; the desktop no longer offers or optimistically reports prohibited hook disablement.
- The interface is organized by operator decision rather than by data container: a grouped sidebar of eight destinations, a 44 pt context bar on every screen instead of a hero header, three-zone data screens with counted facet rails and inspectors, and the repository under repair selected once in the sidebar header.
- Disabling every managed hook, repairing violations with a headless agent, and deactivating local enforcement now open a decision dialog that quotes the exact effect, lists what it touches, and keeps the safe verb as the primary button.
- Mutations report the backend's own sentence in place on the screen that started them, instead of a modal alert that closes without a trace.
#Fixed
tama task scope setdeclares a scope for a session whose prompt Tama never captured. A launcher that hands a session its first prompt at spawn raises no prompt event in OMP, sorecord-current-assignmentrecorded nothing;block-off-task-workthen refused every write namingtama task scope set, and that command refused because no instruction was recorded, so such a session could write nothing at all.setnow records the newest operator-side message of the session's transcript as its instruction, says so on stderr, and binds the scope to it; a transcript that cannot be read or holds no such message is refused with its cause. See off-task work.block-unprompted-topics,check-artifact-inspection,check-failure-inspection,inspect-required-checkandblock-known-to-operatorread the session transcript through the shared JSON Lines reader. The first two parsed it line by line and skipped a line that did not parse, so a damaged record took the operator's words or the session's reads out of the verdict without a word; the other three refused the last line while its harness was still writing it, and the failure-inspection pair also split a record at a U+2028 inside its strings. A complete line that is no record now refuses with the transcript's path and the byte the line starts at; a last line still being written is set aside.check-artifact-inspectionnow finds the session's transcript with the locator the failure-inspection gates use, so it names a transcript it was given that cannot be read, aCLAUDE_TRANSCRIPT_PATHit cannot read, a project transcript folder or entry it cannot list and a transcript whose modification time cannot be read, instead of judging as if the session had read nothing;block-unprompted-topics --explaincounts the records read and prints a block's whole opening line.- The last gates and tools that asked whether a path exists with a question that answers no for a path it may not examine now tell the two apart.
block-external-source-editsrefuses a command whose operand cannot be examined, naming it, where it took the operand for no path and let it through unasked;block-unregistered-build-or-restartrefuses a build whose repository's.gitcannot be examined, or that runs at a folder with no name, instead of registering it under the folder's own name orunnamed-directory;portable-registry-pathsandreconcile-registry-sourcesfind the registry's checkout with one shared lookup, andreconcile-registry-sourcessays on stderr why it found none. - The Brama client names a setting it cannot read.
TAMA_BRAMA_CONSUMER,TAMA_BRAMA_ADAPTER_CONSUMER,TAMA_OBJECTIVE_AUTHORITY_MODEL_ROUTEand the router address variables were read so that a value that is not Unicode counted as unset, and the declared default stood in for the value the operator gave; such a value is now refused naming the variable. A plaintext chat endpoint is allowed where its address is a loopback address orlocalhost, read from the address itself, sohttp://[::1]/v1/chat/completionswithout a port is no longer refused.tama model-provider checknames a successful catalogue answer that is not a model list instead of printing the body as if it were the service's error, and an endpoint that names no catalogue instead of probing the chat path. - Journeys moved out of folders past the five-file limit, under their own names:
tests/numeric-literals/build_directory.rs,consent.rsanddeclared_version.rsare now undertests/numeric-literals/declared/, andtests/violations/fallbacks.rs,mcp_default_guard.rs,owner_clone_dir.rsandstreaming.rsundertests/violations/find/. Their test targets keep their names (numeric_literals_build_directory,violations_streamingand the rest); earlier entries name the files where they stood then. review-ref-movelets a bare repository's branch follow a remote's rewritten history. It asked Git for the checkout's top level, which a bare repository does not have, so a fetch into a package manager's cache (SwiftPM resolving a dependency whose branch was rewritten) was refused withthe moved refs cannot be readand the dependency could not be resolved. See what review-ref-move reads; real journeytests/review/ref_move.rs.- A write that leaves several files is judged one file at a time under each file's own path by
pre-write-edit,block-internal-namesandblock-one-off-commands. They joined every file the write left into one text judged under the first path, so a refusal named the wrong file and a line of the joined text, and a file in a vendored tree named first spared the source files after it. See whole-file judging; real journeytests/pre-write-edit/held_lines.rs. - A waiting judge request's readiness watch asks Brama once per change of its answer. It asked
/readyzagain the moment each answer came back, so every waiting hook ran a client and a request per answer on the adapter carrying its judge request, for as long as the answer took. The watch now names thedigestof the answer it last read (/readyz?after=<digest>), Brama answers when that answer changes, the watch ends its standing question when the request ends, and the question it holds open is attributed to its hook so it never holds another hook's judge back. See cancellation, judge requests and hook supervision; real journeytests/hooks/readiness_watch.rs. - A structural rewrite written to
xd://ast_editis refused by every gate that judges a write by the file it leaves, naming its targets. The gates read the rewrite's request as the written text, and the request holds only patterns, so a file a gate refused to let an edit touch could be rewritten through the device with none of its lines read. See whole-file judging; real journeytests/pre-write-edit/held_lines.rs. - The protected-configuration consent guard reads a shell call by its command alone. It cut every string of the payload — the call's description, its directory, the session's paths — together for shell quotes, so an apostrophe in any of them opened a quote the shell never sees, and a plain commit or register command was refused as a hook edit.
pre-bashreads-pas patch output where a git subcommand declares git's diff output options (--unified), sogit log -p,git show -pandgit diff -ppass on a git whose help names-p, --patch; hunk selection (add,commit,reset,checkout,restore,stash) stays refused.check-artifact-inspectionrefuses a first run whose recordings folder cannot be read, saying so, instead of passing it as a run with no recordings; the refusal lists each work folder it could not read, and an unreadable project transcript folder is named.- One session-transcript locator: the unprompted-topics gate and the objective reviewer use the shared one, which searches every harness's session folders and names a folder, entry or time it cannot read instead of skipping it. Failure-inspection label discovery names an unreadable state file, recordings, evidence or work folder.
tama's JSON answers end the command naming a value that cannot be encoded or a standard output that cannot be written, instead of printingnullor reporting success;tama copiesnames a folder or entry it cannot read for a copy's size in the copy's inspection error instead of counting it as zero bytes; a model judgement whose gateway answer cannot be read names which layer failed.tama hook editor-save-guardjudges the whole file an editor saved. It read at mostWISENT_EDITOR_HOOK_MAX_BYTES, or 1 MiB when that was unset, so whatever stood after the cut passed unread; it read bytes that are not UTF-8 as replacement characters; and when it could not name its own program it ran the hooks throughnodeand the JavaScript dispatcher instead. Now the variable is gone, a saved file that is not UTF-8 text or that cannot be read is refused naming it, and a program that cannot name itself or startrun-hookrefuses the save with that cause. Real journeytests/editor-save-guard/unreadable.rs.- A gate that reads the session's capability says when the capability it was handed cannot be read or is not JSON (
tama: the session capability <variable> is not JSON (…), so it admits nothing) instead of refusing as if no consent had been recorded; it still admits nothing. tama hook editor-watchstarted with no directory refuses withusage: tama hook editor-watch <tree>...and exit1; it watched one workshop's checkout under the home directory, a path that means nothing on another machine.- Block-off-task-work no longer judges what a commit takes: its own commit gate read the whole index, so it refused a commit naming its own paths whenever another session had staged a file, and passed every file no session-writes log named. Pre-bash's commit guard is the one gate, and it passes the deletion of test code no approval covers.
- A tree sweep of
tama violations scan-reposnames a checkout whose GitHub archived state it could not read (PROBLEM github: whether GitHub archived <owner/name> could not be read, so it is scanned: <gh's cause>) and still scans it; the unread list was taken as empty, so every checkout of that owner read as pushable and the sweep as clean. - Hook configuration generation refuses a registry with no
eventsobject (the Tama hook registry has no events object, so no hook can be wired) or an event with no hooks list (the Tama hook registry event <event> has no hooks list).tama provider configanswered such a registry as one declaring no event the provider supports,generate-configswrote empty provider files, and a one-session run called a known hook unknown. A generated entry is always acommandentry; the provider's stop-after field andstatusMessageare copied only when the registry row states them. Real journeytests/provider-config/readback.rs. - The model-provider probe names an answer from
skarbiec grant verifythat carries noallowedverdict, quoting it, instead of reporting that this machine's authority does not grant the route. tama rules digestnames a drafted rule that has noidas an unreadabledrafted-rulesentry carrying the rule; it was left out, so the rule read as never drafted. A field a drafted rule or a lesson source does not carry isnull, printed asnull, instead of a blank ornot named. Real journeytests/learning/registry.rs.tama sessionsrefuses a session-control directory it cannot list withcannot list <directory>: <cause>and exit1; it answered every listing error, and every entry it could not read, as if no session had published itself. A missing directory still lists nothing. A record that cannot be read or parsed stays a row whosereadErrororparseErroris the cause instead oftrue, and a field a record does not carry isnull. Real journeytests/session-records/listing.rs.tama hook editor-watchruns the save guard through its own engine only: when it could not name itself it rannodeand the JavaScript guard, and exited when that failed to start; now that failure is aguard-errorlog line naming the cause. The guard's answer is logged whole instead of cut at 4000 characters. The log rotates only at the sizeWISENT_EDITOR_HOOK_MAX_LOG_BYTESdeclares; it used to rotate at 8 MiB when nothing was declared, raise any value below 64 KiB, and replace a value it could not parse, which is now refused at start (WISENT_EDITOR_HOOK_MAX_LOG_BYTES=<value> is not a byte count: …). A log that cannot be opened or written is said on stderr. Real journeytests/editor-watch/log_size.rs.block-temporary-path-operationsandcheck-git-hooks-pathrefuse a relative path they cannot make absolute instead of judging a path under/: when the directory the hook ran in could not be read, the path was joined to an empty directory. The refusal names the path and the cause (BLOCKED: notes.md cannot be resolved, so whether it is a temporary path is not known: cannot make notes.md absolute: the working directory cannot be read: …); the hooksPath gate says the directory git runs in is not known, or whichcore.hooksPathit cannot resolve. Real journeytests/temporary-paths/fields.rs.tama violations findsays why it skipped a file it could not read (skip <path> (unreadable: <the system's cause>)) instead of onlyunreadable.tama violations cleanno longer accepts a round whose rule-gaming check could not run: agit statusthat cannot start or fails was read as no findings, so the round passed unchecked; now the round is rejected naming git's answer, its patch is undone, and the run ends. A rejected round whose patch cannot be undone names why.tama violations findcounts a registered hook'sblockdecision that gives no reason as a refusal, named with the document the hook printed; it was read as a pass. With--program <path>, a program that cannot be examined is named instead of being handed tobash, a payload the program did not receive whole is an error instead of a verdict, and a program a signal ended is reported with the signal (ended without an exit code (signal: 9 (SIGKILL))) instead of a bareexited -1. Real journeytests/violations/streaming.rs.tama hooks sealrefuses a staged release it cannot fully examine instead of sealing a guess: a file whose mode cannot be read was digested as mode 0, an entry whose type cannot be read was taken for no symlink, a source identity without a revision was sealed with an empty one, an external hook source without an owner folder, hook id or file name was copied under an empty path segment, and apackage.jsonor catalogue that states no version was sealed asunknown. Each refusal names the file or registry entry; a release that states everything digests and seals as before.- A session-control file that exists and cannot be read or is not JSON is named instead of read as absent.
tama sessions revoke-capabilityrefuses with the file and the cause where it answered that no override names the session, although the override it could not read might hold the grant. The hook-path readers keep their fail-closed answer — no session override, hooks enforced, no reminder — and say on stderr which file failed and what they take instead. --helpanswers at every level of a command group with that level's own lines:tama violations find --helpprinted the wholeviolationsgroup, the same text astama violations --help, so no verb had help of its own and a reader of the command tree could not tell a verb from its group. The help now describes the deepest command the words before the first option name, headed by that command.tama helpalso said the summary goes to stderr; it goes to stdout. Real journeytests/cli-groups/groups.rs.- The pre-push quality check judges the pushed commits, not the shared working tree: the dispatcher runs
tama hook quality-checkon every push with Git's pre-push records, which hands them tostado quality check --pre-push --if-declared; without Stado the declared gates run on each pushed commit's tree written out under the checkout's ignored.build/tama-quality/. Another session's uncommitted file no longer refuses a finished commit or lets an unformatted one through. tama violations findsays each step as it happens — which file which hook is judging, each finding, each file not judged, each repository's totals — on stderr, or with--jsonlas JSON lines on stdout; every hook run is named by the wait mechanism while it runs, so a hook that does not finish is no longer a silent scan. Registered hooks' refusals are read from the decision document they print, which the replay had never counted.--hook <id>replays one registered hook; the hook-file override is now--program <path>.tama tests approve,revoke,listandaudit, andblock-unapproved-tests, stop naming the test approvals record when it exists but cannot be read or parsed. Read as approving nothing, an approval rewrote the operator's record with the one new path andaudit --removedeleted test files he had approved.- The gates that place a call's paths read the working directory once and refuse when it cannot be read, instead of joining relative paths to an empty path or
.:block-unapproved-tests,block-external-source-edits(which let such a call through unjudged), the LSI and Overleaf API guards,check-merge-deletion-loss,pre-bash,check-artifact-inspectionandblock-off-task-work;post-trajectory-capturesays on stderr that the run was not registered and why. block-off-task-workandblock-unapproved-testsrefuse a structural rewrite (xd://ast_edit) whose request carries no readable targets, andblock-off-task-workrefuses while the session's assignment cannot be read, instead of judging every scope against an empty instruction.check-git-hooks-pathnames a git that cannot answer for the worktree root orcore.hooksPath, and a hook file it cannot examine, instead of reporting an unset hooks path or a non-executable hook.- The stop gates name what they could not read:
block-untreated-block-stopan unreadable or non-JSON hook registry (every denial passed as none),block-stop-with-claimed-worka claim ledgersqlite3could not read or answered without rows it can use, and the session-transcript search an entry of its folder it cannot read. - The adaptive doctor refuses a repository hook registry it cannot read (
driftreported nothing,repairfound no hook), restoring the runner names an unlistable backup folder and why the copy failed, and the segment index, segment acknowledgement and episode lock name a folder, output or lock they cannot read or take.tama violations findreports a changed file it cannot read asunreadable-changed-fileand counts such a file in its folder for the folder limit. tama hooks installstops with the cause where it carried on past a failure: a path it cannot remove or the runtime link it cannot read (the rollback could not restore it), a registry the provider generator never received on stdin, a release file outside its folder, a hook command whose program names no file, a codesign that cannot run, an OMP extensions answer that is empty or not a list (it was written over the user's extensions), and a manifest without a release id. It takes the Node.js requirement from the release's own runtime preflight, run with each Node.js onPATH, instead of a restated major and three guessed install locations, and names every Node.js it refuses. A release built outside its recorded home says that no repository's own Git hooks are installed.- The write and stop gates name what they cannot read instead of reading it as nothing: the one-program check refuses when git cannot list the repository or a manifest or
src/bincannot be read or parsed;check-failure-inspectionandinspect-required-checkread HOME through the one home reader (not/tmp), refuse an unreadable working directory or inspection state and a registration without its artifact's identity, and name unreadable transcripts, session folders and recordings; the legacy activation bypass check counts a folder or file it cannot read as a violation; the artifact inspection gate says a recording cannot be read instead of reporting a digest mismatch; the CUA and SSH gates name a grant registry they cannot read or parse; the consent quote search names a transcript it cannot read. - Tama Desktop no longer shows Policy active when the emergency hook state or the installed-release record exists but cannot be read or decoded. Posture shows Policy protection cannot be confirmed with the file and the cause, protection is not shown as on, and switching hooks refuses with that cause; only a missing file still means not disabled or not installed. A session-control record that cannot be read refuses the session listing naming every such record, instead of the live session silently disappearing.
tama hooks releasesays the installed runtime's provenance is unknown, with the manifest path and the cause, whennative-hook-binaries.jsoncannot be read or does not state its revision and whether the checkout was dirty; before, it said nothing, and a manifest withoutdirtywas reported as a build with nothing uncommitted.check-rust-formatchecks the Rust files a push changes - those that differ between the remote's current commit and the pushed commit - each withrustfmt --checkunder its package's committed edition, instead ofcargo fmt --checkover every workspace of the commit. A file already unformatted on the remote refused every push of the checkout, including pushes that never touched it, while push review refused the reformat-only commit that would have cleared it; such a file now holds back only the push that changes it, whose author formats it as part of that change. The real journeytests/review/format.rspushes an unformatted file to a remote before the dispatchers are installed, then proves a push that leaves it alone passes and a push that changes it without formatting is refused naming that file alone.- Judge waits stream their route and endpoint to hook stderr and the adapter's progress indicator. A stop reviewer that cannot obtain a verdict refuses with the infrastructure cause. Native dispatch cancellation and input closure terminate owned hook groups without labelling cancellation a hook malfunction; runtime status inspects descendants and signals identity-proven orphans, reporting unsafe legacy records rather than guessing ownership.
tama assignment show, the Stop guard and the build gate read the open defects through the register's own filter (oko defects list --open --json) instead of the whole register with every closed and withdrawn record. The reading's registers are each one call for open items only; the real testtests/assignment/show_registers.rsruns both forms against the machine's Oko, checks the open-defects section against the register and records each form's wall time with the source revision.block-keyword-literalsandtama vocabulary reportread the regex literals of JavaScript and TypeScript files. Only quoted literals were read, so/Try again|Retry/i.test(text)passed while'Try again|Retry'was refused, and the report's count stood still as quoted lists were removed while every such regex classifier stayed. A/is read as a regex where an operand is expected, and after a name only when a method call follows the literal (return /a|b/.test(x)); a division, a URL in a comment and slashes inside a string are not regex literals. The real testtests/keyword-regex-literals/regex_literals.rsdrives the built hook and the report.block-keyword-literalsrefuses a vocabulary written as a comparison, which it let through before: a chain of==against two or more quoted words on one line (word == "okay" || word == "sure",kind === 'task' || kind === 'detail') and the arms of amatches!ormatchpattern joined with|. Each is the same classifier as a list, spelled without brackets. A comparison with one word, an argument list handed to a process in any language and a command line bound to a name still pass. The real testtests/keyword-literals/argument_vectors.rsreads the built hook's decision document for every shape; its refusal assertions had read the exit status, which a refusal never changes, so they could not have passed.block-keyword-literalsreads the name arrays of aCargo.tomlas Cargo's own declaration: a dependency'sfeatures, a workspace'smembers,default-membersandexclude, and a package'sinclude,keywordsandcategories. A manifest whose dependency enabled two features was refused as "a data list of N words" on every edit, whatever the edit changed, so no test or dependency could be added to it. The same arrays in any other file are still judged.block-keyword-literalsreads each step of a Stado catalog recipe'safter_installas the command it is.stado/catalog/products.ymldeclares Tama's own install step as- [tama, hooks, release], and the guard refused every edit anywhere in that file as "a data list of 3 words", so no product's catalog record could be changed. A step list holding anything but sequences of strings is still judged.block-numeric-literalsreads aCargo.toml'sedition,rust-versionandlicensevalues as the names they are. A new Rust package could not be written at all:edition = "2021"was refused as the number 2021 andlicense = "BSD-3-Clause"as the number 3, and a manifest without an edition builds as Rust 2015. The same keys in any other file are still read.block-keyword-literalsno longer reads punctuation inside a string as code when it decides whether a word list is a call's argument. A list of a game's moves handed to a call was refused as a kept vocabulary whenever the description string beside it held a semicolon, and passed once the arguments were reordered.- An open OMP session takes a newer Tama adapter without restarting. OMP imports an extension once per process, so every session kept the adapter it started with after a Tama install, and a gate added since never ran in it.
omp-shared-hooks.jsis now a shell that imports the adapter fromshared-hooks/omp/adapter.js;tama_reload_hook_runtimeloads the installed adapter in place when it changed, and keeps the loaded one, naming why, when the new one fails. A session started before this release needs one OMP restart to get the shell. - A turn no longer ends unchecked because its Stop hooks were slow together. The engine ran every Stop hook one after another, so their times added up past the budget OMP gives a
session_stophandler, and OMP then ended the turn as if no hook had refused. The Stop hooks now run side by side; checking an ending takes as long as its slowest hook, and the refusal text and result records keep registry order. - A model request the text gate forces to call a tool is no longer refused by Anthropic. OMP asks Anthropic to clear old thinking (a
clear_thinking_20251015context edit) whenever thinking is on, and Anthropic answers that edit with HTTP 400 once thinking is dropped (clear_thinking_20251015 strategy requires thinking to be enabled), so every forced request would have failed instead of calling a tool. The OMP adapter now drops that edit with the thinking and keeps every other context edit. - Agent text no longer reaches the operator while Oko holds open work.
block-text-outside-tool-callsrefused text only on Stop, after OMP had streamed and shown it; it now also runs before every model request, and the OMP adapter answers its refusal by requiring the request to call a tool (tool_choiceany for Anthropic, with extended thinking dropped, or required for OpenAI), so no prose is generated. A request whose tools cannot be required is sent unchanged with a line on standard error and keeps the Stop refusal. block-stop-with-open-workno longer resends its whole open-work list on every Stop. When OMP's last stop continuation in the session already carries the same refusal and the operator wrote nothing since, the gate holds the turn with one line naming the next item to take; an operator message, or open work that changed, brings the full text back. One session received the same list about four hundred times, each a full turn with the whole list as input.- The stop gates no longer print the same refusal on every ending.
block-idle-answer,block-stop-on-own-work,block-text-outside-tool-callson Stop, and a reviewer-backed gate whose model route is down (TAMA_HOOK_INFRASTRUCTURE: … review unavailable) now give their refusal the wayblock-stop-with-open-workdoes: in full the first time, as one line naming the next item while the session already holds it, and not at all once that line stood and no tool was called since, so control goes back to the agent instead of the same line again. The harness's own continuation injected as a user-role message attributed to the agent is read as a continuation, not as the operator's word: it carried the refusals of every gate and was read as a new message, so the full open-work list came back several times in one session. A message the operator typed still brings the full text back; before a model request the text gate still refuses in full, since the adapter reads that refusal, not the operator. - A stop refused with exactly the refusal the session's previous stop got is no longer printed again, whichever gates gave it and on every harness. The core keeps each session's last turn ending in
turn-end.<session>.jsonbeside its control files (TAMA_SESSION_CONTROL_ROOT, or Tama's session-control directory); a repeat of the same block keeps the turn held and is sent as one line,BLOCKED: the same refusal as this session's previous ending, given N times in a row, is not printed again; its full text is `decision.reason` in <path>, while the record keeps the full refusal and the times it was given (givenAtMs). A different refusal, or one after a stop that passed, is given in full.block-stop-with-open-worklists the open defects and tasks once per session (recorded inopen-work-listed.<session>.json); every later refusal of the session counts them, names the next item and points tooko defects listandoko tasks owed. Real journeytests/stop-gate/identical_endings.rs: two identical endings through the real core, the second sent as the one line, the record holding both. block-numeric-literalsreads a release version that is a whole path segment of an address as an identifier, as it reads a quoted version: a formatter's$schema(https://biomejs.dev/schemas/<version>/schema.json) or a package's release page cited in a comment was refused, so Weles could not pin a formatter at all. A segment with fewer than two decimal points (/api/2.5/) is still a number and is still refused.block-numeric-literalsreads.cjs,.ctsand.mtsfiles as code. They were scanned as data, so a number in a comment, a string or a regular expression's backreference of a CommonJS module was refused and a comment citing a constant's source was never read; a bare constant in them is still refused with its line.- Installing or reloading a Tama release no longer drops the consent the operator gave in the conversation. The OMP adapter bound a session's capability to the release it was recorded under and nulled it on every reload, so a build, test or hook-edit grant vanished at each
stado product install tamaand the next build was refused as if he had never agreed. The adapter now rebinds the grant to the release it loads, on reload and when a session resumes, with its lifetime and expiry unchanged. - Every Oko call reads the one task-ledger group
oko tasksand passes--jsonwhere it parses the answer; refusal texts and routes nameoko tasks …instead ofoko transcripts tasks …. Install Oko with this release: an older Oko does not answeroko tasks --open … --json. - Completion enforcement now reads Oko's authoritative task ledger using the native OMP session identity. It preserves every executable open request and consumes independent saved verdicts rather than allowing a second Tama model to overrule them. Activation names ledger availability and pending work separately; unavailable evidence fails closed.
- Real OMP completion journeys retain actual Brama exchanges, tool results, source identities and persisted task verdicts. They cover real delivery, refusal beyond the host's advisory continuation cap and explicit interruption. The host fix is a separate OMP dependency, not implied by installing hooks.
- OMP session records and overrides now use the documented v2
agentIdidentity, allowing the desktop to discover live sessions and exchange validated controls; a legacy-only v1 state now produces an actionable runtime-reinstall diagnostic instead of an unexplained empty session list. - Session-discovery failures remain visible instead of being represented as an empty successful result.
- Optional violation tooling no longer depends on a hard-coded maintainer directory.
- Nonzero cleanup-agent exits cannot be reported as successful cleanup even when partial edits remove the final violation.
- Hook registration now rejects invalid wait-limit and registry-path arguments before mutation, reports registry-load, registry-I/O, or mutator-launch failures without an unhandled stack trace, preserves registry permissions across atomic replacement, and retains the primary error if temporary-file cleanup also fails.
- Session control now labels hook-registry load failures explicitly and displays the runtime diagnostic instead of allowing a scheduled or required reload label to hide the error.
- Runtime prerequisites now consistently distinguish local Node.js execution, Python-based installation/restoration, and macOS backend approval across README, onboarding, core, and integration contracts.
- Build, CLI, and runtime installation now reject unsupported Node.js versions; installation validates Node before managed writes, canonicalizes its executable through symlinks, pins that path into installed hook commands and the supervisor launcher, preloads a sealed version guard before every installed target and supervised semantic dispatch, records path and validated version in release provenance, exposes them in Overview, and POSIX-quotes generated shell paths without expansion.
- Release packaging and publication now reject signed
vtags that are not strict Semantic Versioning, including leading-zero numeric identifiers; publication also verifies the sidecar against the artifact digest, byte size, signed embedded build/hook identities, source revision, dependency pins, channel, and canonical examples before upload. - Release packaging and publication rejection paths now return failure statuses instead of allowing automation to mistake a printed diagnostic for success.
- Prerelease and build metadata remain in Tama's exact embedded product identity while Apple bundle versions use the required numeric SemVer core; packaging and publication reject drift across the app, Network Extension, tag, manifest, or component build number.
- Channel promotion no longer implies relabeling prerelease bytes as stable: an exact preview remains preview, while a stable SemVer is a distinct signed candidate that must be qualified before one-time publication.
- Packaging and publication accept an explicit
TAMA_RELEASE_TAGto select one signed release identity when preview and stable candidates share a source commit, reject ambiguous implicit discovery, and reject a selected tag that does not resolve toHEAD. - Publication rejects duplicate ZIP member names, absolute or parent-traversing member paths, and any archive root other than
Tama.appor optional__MACOSXresource-fork metadata before upload. - Packaging and publication both require the app to pass signature, stapled-ticket, and Gatekeeper assessment; publication repeats those checks from the exact zip before upload.
- Publication now requires an immutable qualification sidecar bound to the exact candidate, complete coverage of each canonical example and required suite kind, repeated identity and cleanup evidence, passed/redacted records, and an asset link injected into release notes.
- Publication requires exactly one matching release-notes section and rejects missing, duplicate, reordered, or empty canonical categories before creating the immutable GitHub release.
- GitHub publication now requires the repository immutable-release policy, creates and retains one stable GitHub release ID, verifies the exact remote signed-tag object, stages exactly the four canonical assets in a private draft, rejects missing or foreign asset names, validates ID, title, tag, notes, draft state, and preview/stable classification, downloads and compares every uploaded byte, rechecks policy immediately before publishing that exact ID with an explicit stable-only
latestdecision, confirms the published metadata and immutable state, redownloads and rechecks the locked asset set, reports the stable ID after failure, and never automatically deletes a GitHub release where draft state cannot be an atomic deletion precondition. - Remote asset verification now paginates the retained release ID's complete asset collection and downloads every canonical byte stream by its unique release asset ID before and after publication.
- Canonical assets are now uploaded through URLs bound directly to the release ID returned by draft creation; upload and verification no longer resolve a mutable release tag.
- Release publication now classifies preview versus stable from the SemVer prerelease component only, so hyphens inside build metadata cannot suppress a stable release or mark it prerelease.
- App builds now read valid Keychain signing identities once and require an exact certificate-name or case-insensitive hash match, while also rejecting unsupported channel or code-signing timestamp modes and missing app or Network Filter provisioning-profile files before compilation. Build output and optional installation are assembled and signature-checked in private staging directories, then promoted with sibling backups and rollback instead of deleting the previous bundles before fallible work completes.
- Violations command output is retained within explicit bounds and output-limit failures remain visible.
- Cleanup rejects changed HEAD, checked-out branch, or local branch refs and states the remaining external-provider verification boundary.
- Backend status exposes partial installation, and deactivation attempts every privileged component while preserving aggregate failure and restart-required recovery.
- Backend unavailability is reported as a full-width panel carrying the backend's literal sentence and a reproducing command, rather than a dismissable alert or a swallowed read; the catalog, session control, provider coverage, install plan and repository scan each name their own command.
- The session capability document was decoded from every session record and displayed nowhere.
- A privileged backend that is merely not registered, a provider with no declared mapping, and a justification whose evidence is incomplete are no longer coloured as failures.
- A refresh that fails keeps the previous read on screen instead of replacing it with an empty state.
#Removed or deprecated
tama build consent ask, the compile gate's reading of the operator's answers to build-consent asks (~/.shared-hooks/build_consent_spent.json), and their listing intama attestation list --kind build: no build is asked of him.tama build consent …is refused as an unknown action; his standing rulings and the build capability he gives in the conversation are the only consents.- Automatic service registration on application launch.
- The GitHub release publisher (
Scripts/release/package-release.sh,publish-release.shand their Python helpers): Tama Desktop releases through Stado. block-unapproved-audits' per-scope gate record: it was written on every audit and read only by a branch that could never run.- Automatic session-controller installation on application launch.
- Supported-runtime dependence on
~/Documents/CodingProjects/Wisent/tama. tama adaptive claude-config: a second, one-provider spelling oftama provider config --provider claude, which prints the hook block for every provider from the one event table.tama patterns extract: it moved a guard's table of strings out of Rust into a JSON declaration, which keeps the same word list in a data file thatblock-keyword-literalsjudges like any other list. A behaviour that classifies by words is replaced by reading the structure it acts on, not by moving the words;tama vocabulary reportlists the tables still standing.tama merges review, the desktop requestmerges/reviewand the Desktop's Merges screen: they matched the commit subject one removed branch merger wrote and listed the damage its merges left, a reader of that one tool's history. Files over the line limit are whattama violations findreports on any tree.tama adaptive installandtama adaptive uninstall, with the hiddenadaptive-runnerentry they wired: install replaced the dispatcher a release install writes with a shell shim, which the provider configurations run under node; uninstall put a backup back. The release install (tama hooks release) writes that dispatcher on every install and is the one way it changes.
#Security
- Policy-changing setup is moved behind explicit user intent.
- Read-only catalog inspection remains available during Wisent Auth outages without exposing control actions.
- Distributed builds ignore development hook-root and Node executable overrides; unauthenticated inspection does not load local justification or policy state.
- Release artifacts record source provenance and carry independent SHA-256 verification.
#Configuration
- Developer-only source overrides remain unsupported for binary releases.
- Writable cleanup state is kept outside immutable release bytes.
#Data or state migrations
- Installed hook-release state and existing v2 session records remain readable. Legacy v1 session-control records and overrides are deliberately ignored because they lack the v2
agentIdidentity contract; reinstall the bundled runtime, then stop or resume affected sessions so the supervisor publishes fresh v2 records. - The former
tama.hasSeenWelcomeandtama.hasSeenTrustScreenpreferences are no longer read. Existing installations retain only independenttama.hasCompletedSetupstate without changing installed policy.
#Compatibility requirements
- Apple-silicon macOS 14 or newer.
- Node.js 20 or newer for installed runtime and violations workflows.
#Operator actions
- Add
call:brama#best-decision-modelto Tama's consumer token (skarbiec token-mint tama --capabilities call:brama#best,call:brama#best-decision-model): typed questions ask that alias, and Brama refuses a token scoped tobestalone with403. - Review setup permissions and explicitly install/register components after upgrading.
- Confirm product build identity and loaded hook release before resuming supervised sessions.
#Known limitations
- No supported preview artifact has been published yet.
- Intel macOS, Linux, and Windows have no supported policy backend.
xd://tama_request_session_capabilitystill puts a build grant an agent writes in front of the operator's window: its device source cannot be changed until it is split below the size and fallback gates every edit of it meets.
#Qualification evidence
- The release evidence (
evidence/release.json,evidence/notary.json,evidence/DIGESTS) Stado stages with each release; qualification records what the candidate was run against before it is promoted.