This release is a pre-release and may not be stable for production use.
assent — an AI plan format + an automatic scheduler
Traditional Chinese reader edition
A file-based system that lets an AI work correctly on long-running projects with minimal context, plus a scheduler that understands that system and runs it unattended.
- Planning: a human and an AI hold a meeting session; consensus is
immediately fixed into task files under
.assent/, and adjournment =assent checkpasses. - Execution:
assent runfinishes every task unattended — picking a task, opening a headless AI session, running its focused verification, committing a git checkpoint, waiting out quota exhaustion and resuming. Once a folder is complete, the scheduler runs the full candidate verification outside the AI session; the scheduler loop itself burns zero tokens. - Review: a human reads the program-generated
.assent/<work folder>/_report.md(zero tokens), and opens a session only for the tasks that need a decision.
Design principles
- Minimize token consumption while keeping output quality trustworthy.
Scheduling, review, and reporting are all local, pure-Python work; every AI
session's required reading is only the project
AGENTS.md+ the assent working instructions + its own task file. - Stay flexible, less is more. Zero third-party dependencies (standard library only); the task file itself is the state — no database, no hidden state.
- Automate everything an AI can handle; humans only review and decide. Humans never hand-edit files; when a review fails, they issue instructions for an AI to make the change.
- Token-burned output from the executing AI is never discarded.
A quota interruption is collected into a
wipcheckpoint and resumed; a failed review is not reverted, and retried on top of the existing results; once retries are exhausted, the results are committed into aBLOCKEDcheckpoint for human adjudication.
How it works
┌────────────────────────────────────────────┐
│ main loop (zero tokens) │
.assent/ │ 1. Scan work folders, pick a task: resume │
work folder ─▶ WIP first, otherwise the first TODO │
(tNNN_name │ whose upstreams are all DONE/SKIP │──▶ executing AI
.e.toml) │ 2. Read that task's tier/effort, open a │
│ headless session │
│ 3. Objective review after the session ends: │◀── updates the task
│ status → structural diff (tamper guard) │ file + the
│ → scope → verify │ matching
│ 4a. Pass → auto(work-folder/tNNN) checkpoint │ .r.toml log
│ → back to 1 │
│ 4b. Fail → keep results, retry with reason │
│ → still failing → mark BLOCKED, commit │
│ results together → back to 1 │
│ 4c. Quota exhausted → wip checkpoint → │
│ countdown until reset → resume with a │
│ "continue" prompt │
└────────────────────────────────────────────┘
- The task file is the state: each task is one
tNNN_name.e.tomlfile (status, dependencies, tier, scope, verify, acceptance conditions); its log is the same-stemtNNN_name.r.toml(append-only, not read by default). After a handled interruption records WIP, runningassent runresumes that task. An abrupt process or host failure can instead leave a dirty worktree; the scheduler then refuses to guess until you review and checkpoint it. - Format contract:
~/.assent/format.md(installed byassent initinto the per-user assent home, once per machine) is what a planning AI reads to produce task files, and what the scheduler's parser is aligned with byte-for-byte. Its sibling~/.assent/instructions.mdholds the session rules. Neither is copied into a project. - The session is visible live: what the AI says (
AI|), the tools it uses (Tool|), and token usage (--|) print to the terminal in real time and are kept in.assent/<work folder>/_assent.log.
Installation
Python 3.11+, git, and a logged-in Claude Code CLI (claude) or Codex CLI
(codex).
python -m pip install assent
Verify: run assent --version from any directory; it prints the installed
distribution version. assent --help shows the top-level CLI help. Zero
third-party dependencies — nothing else gets downloaded.
For development from a source checkout:
python -m pip install -e .
Where files live
What describes assent itself lives once per machine; what describes your project lives in your project.
~/.assent/ # the per-user assent home, shared by every project
├── assent.toml # your settings: adapter, tier tables, watchdog, retries
├── instructions.md # session rules contract (assent owns this file)
└── format.md # plan format contract (assent owns this file)
<project>/
├── AGENTS.md # your project rules + one assent bridge line
└── .assent/ # git-ignored, main worktree only
├── verify.py # your project's verification script
├── assent.toml # optional: a legacy or deliberate project override
├── <work folder>/ # task files, r files, _report.md, _assent.log,
│ # assent.lock, and that folder's verification receipt
├── _batch_verification.toml # the batch verification receipt (derived)
├── _archived.toml # roster of retired work folders
└── _archive/ # those folders, compressed one zip each
instructions.md and format.md describe the tool, so there is exactly one
copy of each on the machine and a project never receives one. AGENTS.md and
.assent/verify.py stay yours: assent init refreshes only the single bridge
line in the first and never overwrites the second.
Settings precedence
Lowest priority first:
- assent's built-in defaults
- your user settings in
~/.assent/assent.toml - the optional project override in
.assent/assent.toml - an explicit CLI selection where the command offers one (
--config PATHchooses which project-level file plays role 3;--jobsand similar flags override that setting for one invocation)
Tables merge by key; scalars and arrays are replaced whole. A project override
therefore shadows later edits to your shared settings for exactly the keys it
states, and assent init never migrates it into the user home or edits it —
it is preserved byte for byte and reported as an override.
Omitting a key is the only way to inherit the layer below:
key =is not "no value" — it is invalid TOML, and the file fails to load.- An empty table states no leaf override, so every key inside still resolves from the layer below.
- An empty array is an explicit replacement wherever that field permits one, not a request to fall back.
- An empty or whitespace-only string is refused for any setting that needs useful text (a command, an adapter name, an effort value). The error names the dotted key and the file that stated it instead of quietly reinstating a lower layer.
What assent init does on a repeat run
It refreshes both user-home contracts to this installation's packaged text and
adds only the packaged settings keys your assent.toml does not already state,
never replacing a value you wrote. In the project it preserves an existing
verify.py (and refuses --test when one exists), keeps the bridge line in
AGENTS.md current, and keeps .assent/ in .gitignore. Every read, parse,
and merge finishes before the first write, so invalid TOML or an invalid
--test choice refuses without leaving anything half-upgraded.
Migrating an older project prints warnings rather than making the decision for
you. A project copy of instructions.md or format.md is removed only when it
matches the packaged text exactly; one that differs is kept and reported, since
sessions read the user-home contract either way — move anything you still want
out of the local copy, then delete it. An existing .assent/assent.toml is
kept and reported as a compatibility override that outranks your user settings.
Before opening any session, assent fails closed unless both user-home contracts
are present, readable, and byte-identical to this installation's packaged text.
A missing, unreadable, or stale contract names the path and points at assent init; it is never patched or silently regenerated mid-run. The comparison
reads text with universal newlines, so a file an editor rewrote with CRLF still
counts as the same contract.
Quick start
# 0. cd into the target project root (must be a git repo)
# 1. Install the user home (~/.assent: shared settings + the two contracts)
# and the project's .assent skeleton and AGENTS.md, selecting the real
# project test interactively (or use --test in scripts, for example):
assent init --test unittest
# Choices are parallel unittest, pytest, npm test, Flutter test, or a
# custom argv command such as: assent init --test "custom:python -m unittest"
# Repeat init does not prompt: it preserves the project's verify.py,
# refreshes the two user-home contracts, and adds only missing settings keys.
# 2. Review the shared settings in ~/.assent/assent.toml (every project on this
# machine reads them), then fill in AGENTS.md's project description/hard
# constraints and .assent/verify.py's actual check commands
# Whether AGENTS.md is committed is up to the project;
# the whole .assent/ stays in the main worktree and is not committed
# 3. Hold an AI meeting to produce task files (an interactive session; see
# "Usage loop" below)
# 4. Validate the plan and environment (zero tokens; passing = the meeting
# can adjourn)
assent check
# 5. Try one task, confirm it's correct, then run everything unattended
# (can run overnight)
assent run --once
assent run
# The work folder can also be given as a positional argument
# (orthogonal to --config)
assent run <FOLDER>
# Run exactly A, then B, in the order written
assent run A B
# Run A and B in that order, then hand the rest to the --all scheduler
assent run A B --all
# The literal `...` is the remainder selector: run A, then B, then every other
# work folder, as one selection snapshotted before anything starts
assent run A B ...
# Run every incomplete folder in dependency order, at most 2 folders at once
assent run --all --jobs 2
# Chain complete verification onto a run that exited zero (a failing run
# verifies nothing); the verification's exit code becomes the exit code
assent run --all --verify
# 6. Under the default [verification] receipt_refresh = "manual", run closeout
# leaves no receipt behind, and direct/selected acceptance is refused with a
# prompt to verify first. Refresh it explicitly while away (zero tokens):
# verifying several finished folders as one candidate costs one full
# verification instead of one per folder
assent verify --batch
# Or refresh just one folder's receipt
assent verify <FOLDER>
# Run complete verification for exactly A and B as one dependency-ordered batch
assent verify A B
# Verify A plus every other finished folder as one exact selected batch
assent verify A ...
# Rerun DONE-task focused checks in FOLDER's source worktree (no receipt)
assent verify <FOLDER> --focus
# Set receipt_refresh = "auto" instead if you want run closeout to refresh
# the receipt itself
# 7. Check in any time (a separate terminal, zero tokens), then review
assent status
assent report
# After human review, accept every finished folder in dependency order
assent accept --all
# Or accept just one completed folder into the current target branch
assent accept <FOLDER>
# Accept exactly A and B from a matching verified batch receipt
assent accept A B
# Accept A plus every other finished folder; still an exact selection, so it
# needs a receipt for exactly that expanded set and never verifies
assent accept A ...
# After acceptance, optionally sync with ordinary Git (or your own AI workflow)
git push
# Once acceptance and any desired sync are complete, remove redundant artifacts
assent clean <FOLDER>
# Clean several folders, upstream-first (`assent clean` alone still means all)
assent clean A B
assent clean A ...
# Once a folder is no longer needed, retire its plan into _archive/
assent archive --all
# Or name the folders to retire; an ineligible named folder is a failure,
# unlike --all, which skips it
assent archive A B
# When a review meeting orders a single task redone (keeps code by default;
# does not run automatically)
assent rework <FOLDER> <TASK> [--cascade] [--reason TEXT]
# When a review meeting rejects an entire folder's implementation
# (archives it, force-deletes it, resets tasks to TODO)
assent reject <FOLDER>
Human review after a run finishes:
git log --oneline <folder name>/<run-id> # one commit per task, review one by one
git diff main...<folder name>/<run-id> # or look at the overall diff
# The human decides; Assent performs the guarded local integration
assent accept <folder>
# Then choose your own ordinary Git sync, such as `git push`, or an AI you delegate to
# Reject a single task → assent rework <folder> <task>
# There is downstream work already started → add --cascade
# Confirmed you want the code reverted → add --revert-code
# Reject the whole folder's implementation → assent reject <folder>
rework immediately updates _report.md on success, but does not print the
full report or start an AI; only after the human confirms the reopened TODO
and its blast radius are correct should they explicitly run
assent run <FOLDER>.
DONE is the executing AI's completion claim, not human approval. A human
must first read _report.md, inspect the report and checkpoint evidence, and
then make the acceptance decision explicitly. The receipt is the scheduler's
complete-verification evidence; accept is the human approval.
Direct assent accept <FOLDER> and selected assent accept A B never run the
complete verifier. A direct folder that is already contained in the target is
an ancestry-proven idempotent no-op; otherwise the direct form requires a
fresh PASSED per-folder receipt whose source tip, reconstructed integration
tree, and verifier digest match exactly. The selected form requires a fresh
PASSED batch receipt for exactly the dependency-ordered set named in
assent accept A B. Missing, malformed, stale, mismatched, or drifted
evidence refuses and points to the corresponding assent verify command.
Neither form silently verifies or accepts folders outside its explicit target.
assent accept --all is the intentional exception and has two modes. A fresh
PASSED batch receipt is replayed and released atomically, without a new full
verification, for exactly the folders recorded in that receipt. A missing or
expired/non-PASSED batch receipt selects the sequential path: in dependency
order it runs verify_folder_if_needed before each not-already-integrated
folder, then performs the ordinary receipt-backed accept. A malformed batch
receipt refuses instead of falling back. The sequential path treats an
already-integrated folder as an ancestry no-op, skips a finished folder only
when its source branch and worktree were both cleaned after proven integration,
stops on the first real failure, and preserves earlier publications. The fresh
batch path only reports finished folders outside its receipt; it does not
verify or accept them in the same run.
All acceptance paths require explicit human action, a complete and
dependency-safe source, and a clean, uniquely identified Git state. They keep
the source for inspection and cleanup, never auto-resolve conflicts, and do
not connect to remote hosting, pull, rebase, force-push, delete source, or
write the target on a failed gate. The integration lock serializes Assent
accept operations but cannot stop unrelated external Git writers; do not run
writing Git commands in the same main worktree during acceptance. Run
assent clean <FOLDER> only when the accepted source is no longer needed and
the cleanup proof is available.
Bounded optimistic stacking
Set a downstream folder's _folder.toml to after = ["A"] to declare A as an
ordering prerequisite. base = "A" declares that the downstream files are
built on A's commit and makes its worktree a complete checkout of that commit;
a non-base after upstream guarantees order only, not file content or
same-file conflict protection. Without a declared base, a folder starts from
the current integration target; the number and acceptance state of after
members do not affect that base selection, and multiple unaccepted upstreams
do not create a base ambiguity or refusal.
For example: run A -> run B stacked on A -> combined verification ->
human accept A -> human accept B. B's receipt may be created before A is
accepted and reused after A enters the target when source tip, integration
tree, and verifier digest still match; accept does not rerun the complete
suite. If A advances, B is stale but its work is retained: rework or reject B,
or open a new folder and replan it. Assent never rewrites stack history.
The same rule applies when A and B edit the same file. Git may merge changes automatically and exact-tree verification proves the result; a conflict leaves the target unchanged for human resolution. Assent does not automatically rebase, resolve conflicts, or push.
Explicit selected workflows
assent run A B runs exactly the two named folders in the stated order. Each
folder still checks its own prerequisites, and the command stops on the first
configuration or run failure. assent run A B --all first completes that
explicit sequence, then hands every remaining incomplete folder to the normal
dependency-ordered --all scheduler. Neither command verifies a full
integration candidate or accepts anything as a hidden side effect.
Every explicit folder selection is audited before dispatch. Each stated name,
including an explicit prefix before ..., must resolve to an existing
.assent/ directory containing at least one formal tNNN_name.e.toml task
file. If any name is unresolved, Assent reports the complete unresolved set and
returns nonzero before the first selected folder runs, verifies, publishes,
cleans, or archives; it also does not create the missing folder, lock, or
per-folder log. This is an identity check only: task completion, dependency
readiness, locks, receipts, Git state, and other eligibility remain each
command's own gates. Omitted folders, --all, --batch, and a bare ... keep
their existing discovery contracts. archive --restore FOLDER and recognized
archive crash-resume states may intentionally have no live directory.
The ... remainder selector
The literal ASCII token ... is a final positional argument shared by run,
verify, accept, clean, and archive. It means "and every remaining
folder this command would discover", so assent run A B ... is "A, then B,
then everything else". It is not a spelling of --all: ... produces one
exact folder selection, snapshotted before anything is mutated, while --all
keeps its own dynamic whole-project mode. Combining the two is a usage error,
as is giving ... more than once or anywhere but last. Folder names may never
contain .., so the token can never collide with a real folder.
Each command expands the remainder by its own discovery rule. verify and
accept add only finished folders, because that is the set their whole-project
modes work on. run, clean, and archive consider every work folder and
make their usual per-folder decision afterwards. The remainder is appended
after the explicit prefix, and each command then applies its own ordering:
run keeps the stated prefix order and takes the remainder in
folder-dependency order, verify and accept normalize the whole selection to
dependency order, and clean normalizes it upstream-first. The expanded
selection is printed before the work starts, and an expansion that selects no
folder is refused. ... selects folders; it does not switch a command over to
another mode, so even a bare assent run ... walks the expanded selection
through the ordinary explicit-folder path rather than the --all scheduler,
and --jobs remains an --all option.
Cardinality, not the token, still picks the path: one folder is the
single-folder path (a folder receipt, a direct accept, one archive), and two
or more is the exact selected batch. So assent verify A ... writes a batch
receipt for exactly the expanded set, and assent accept A ... requires a
fresh receipt for exactly that same set and still never verifies. ... is
rejected with verify --batch, verify --focus, run --once, run --task,
and archive --restore, which restores exactly one folder.
run --verify
assent run --verify chains one complete verification onto a run that exited
zero; a failing run is returned exactly as it is and verifies nothing, because
there is no finished plan to certify. The verification matches the selection:
| Invocation | What is verified |
|---|---|
assent run --verify (auto-selected folder) |
that folder's receipt |
assent run A --verify |
A's folder receipt |
assent run A B --verify |
A and B as one selected batch |
assent run A ... --verify |
the exact expanded selection as one batch |
assent run --all --verify |
the whole-project dynamic batch |
assent run ... --verify |
the whole-project dynamic batch |
assent run A --once --verifyassent run A --task t003 --verify |
A's folder receipt, but only if the limited run left A complete |
A bare ... is a whole-project request and therefore happens to land on the
same dynamic batch --all uses, rather than freezing a set the scheduler may
still extend; an explicit prefix plus ... stays an exact expanded selection
and is verified as exactly the folders it ran. The verification's exit code
becomes the command's exit code.
--once and --task may be combined with --verify. They select exactly one
folder, so the receipt scope is unambiguous, but they stop after a single task:
the request therefore verifies only when that limited run left the single
selected folder complete — every task DONE or SKIP. An incomplete folder
fails the request without writing a receipt; the refusal names the incomplete
task ids and statuses and happens before any integration candidate is created
or any full verifier starts, so it is a failure rather than a silent skip.
--verify is an invocation-level request that runs regardless of the configured
receipt_refresh policy.
Multi-folder clean and archive
assent clean A B and assent clean A ... clean several folders in one
upstream-first pass, with every folder's own evidence rule unchanged; bare
assent clean still means all folders. assent archive A B archives each
named folder and keeps single-folder archive's contract rather than
--all's: the human named those folders, so a folder that is merely
ineligible is a refused request. Every named folder is attempted, a summary
line reports how many were archived, and the command exits nonzero if any was
not — while assent archive --all skips an ineligible folder without failing.
archive --restore FOLDER reverses exactly one archive and takes neither
--all nor ....
Colored help
Where the standard library colorizes --help (Python 3.14 and later), assent
re-themes only the usage: prefix and the section headings, so they are not
the barely legible dark blue of the default theme; every other option and
label color is the standard one. The swap happens inside argparse's own color
decision, so NO_COLOR, FORCE_COLOR, PYTHON_COLORS, and a redirected or
unsupported stream still decide whether any escape sequence is emitted at all.
On Python 3.11-3.13, whose argparse has no color support, help is plain text.
Color is therefore never promised — help stays readable without it.
assent verify A B selects exactly A and B, normalizes them to dependency
order, builds one integration candidate, and runs the complete verifier once.
It writes one batch receipt for the selected source identities and intermediate
trees; a selected merge conflict refuses rather than skipping or shrinking the
set. It never changes the target ref and never accepts a folder. If a failed
request is bisected to a passing prefix, the command still returns failure and
that prefix cannot authorize the original selected acceptance.
assent verify <FOLDER> --focus is different: it runs the distinct DONE-task
verification commands in that folder's source worktree. It creates no
integration candidate or receipt, and even a passing result cannot authorize
acceptance. After a successful exact selected verification, human review may
run assent accept A B; that command requires the fresh receipt for exactly A
and B, replays it without running verification, and publishes all selected
folders atomically or none.
Cleanup is upstream-first and evidence-based. Source evidence is retained
while a direct dependent is unfinished, unaccepted, dirty, missing, or not
provably integrated; assent clean A refuses and explains why. After every
dependent is accepted and provably integrated and clean, clean upstream and
then dependent with assent clean; never manually delete worktrees or branches.
Interactive conflict skipping in verify --batch
A conflict-free assent verify --batch stays fully unattended. Building the
batch candidate is where a source conflict is discovered, and it is never
treated as a verification failure: every queued folder's merge is still
attempted, so one folder conflicting does not stop a later, independent
folder from being tried too. When one or more folders conflict, verify --batch reports every conflicting folder with its conflicting path(s),
reports every folder queued after a conflicting one as excluded with it
(transitively, rather than verified without the upstream it depends on), and
then asks a single [Y/n] question offering to skip that whole excluded set
and verify only the remaining, still-mergeable folders.
- Yes (an empty answer or
y/yes): runs one full verification over the smaller subset and records only those verified folders in the batch receipt; every skipped folder is left out entirely, not attempted. - No, an unrecognized answer, or EOF (a non-interactive caller with no one
to ask):
verify --batchstops before running the full verifier and writes no receipt, same as any other refusal. - Every queued folder conflicts: there is nothing independent left to offer, so the batch refuses outright without asking.
Skipping is not resolving, rebasing, accepting, or deleting anything — the
target and every source folder, skipped or merged, are left exactly as they
were. The conflicting folder's own source still needs a human decision
through assent rework or assent reject before it can rejoin a batch.
assent accept --all has two deliberate modes. With a fresh PASSED batch
receipt, it publishes exactly the receipt's own folders in one atomic ref
update, then reports — in that same run — every other finished folder the
receipt does not cover without verifying or accepting those leftovers. There
is no second prompt or hidden expansion of that receipt. With no batch receipt,
or with expired/non-PASSED batch evidence, it instead takes the sequential
folder path and runs verify_folder_if_needed before each not-already-
integrated accept. A malformed batch receipt refuses rather than falling back.
That sequential path skips folders whose source was already cleaned after
proven integration, stops at the first real failure, and keeps earlier
publications. Run assent verify --batch again to build the next explicit
batch when the receipt-release path is wanted.
assent archive --all only archives a folder that is independently eligible
(complete, and either its source is already gone or clean's own mechanical
proof can remove it); it retains the source evidence, and skips archiving,
for any folder whose source an unaccepted dependent still needs, the same
upstream-first rule clean enforces.
Resolving one folder's conflict with assent reconcile
assent verify --batch can only skip a conflicting folder; it cannot resolve
it. assent reconcile FOLDER is the single-folder counterpart that lets a
human resolve that conflict by editing files only, while Assent owns every Git
operation around those edits. The whole sequence is:
assent reconcile parallel01 # prepare the conflict in a worktree
# (edit the reported files by hand)
assent reconcile --continue parallel01 # stage, commit, advance the source
assent verify parallel01 # required before accept, explicit, expensive
assent accept parallel01 # explicit human approval
Start requires a finished folder (every task DONE or SKIP), a clean
main worktree, and a source folder with its own branch and worktree. It
captures the integration target's current tip, creates the worktree
<project>.reconcile/<FOLDER> next to the main worktree on the temporary
branch assent-reconcile/<FOLDER> starting at the exact source tip, and merges
the captured target tip into it without committing. Because the merge is built
source-first, its first parent is the original source, so the source branch can
later be fast-forwarded onto it — the source is never rewritten and the
integration target is never changed. The main worktree and the folder's own
source worktree stay clean throughout. If the two sides in fact merge without
conflict, start says so, undoes the merge, removes what it created, and leaves
the source untouched. If the source is already contained in the target, there
is nothing to reconcile.
You edit, and run no Git commands. Start prints the worktree path, the branch, both tips, and every conflicting file; resolve those files in that worktree only.
--continue stages exactly the paths Git still reports as unmerged,
validates the result (no remaining unmerged path, no leftover conflict marker
or whitespace error per git diff --cached --check, and no edit outside the
conflict-resolution scene), creates the merge commit, fast-forwards the source
branch inside its own worktree, and then removes the temporary worktree and
branch through the same link-safe cleanup boundary. It proves ownership of each
managed resource again before deleting it —
worktree of this repository, attached to the managed branch, HEAD at the
proven commit, clean — so it can never widen the deletion. Because the source
has really advanced, --continue deletes the receipts that were written
against the old source identity: the folder receipt, and the batch receipt if
any source identity it records is no longer current (a batch receipt is
all-or-nothing). A batch receipt it cannot even parse is left in place for
inspection rather than erased.
Reconciliation is not evidence and not approval. --continue runs no
focused task tests and no complete verification, and writes no receipt. Proving
the resolved source is a later, explicitly human-started assent verify FOLDER
— the expensive step, run against the then-current target — and approving it is
a later assent accept FOLDER, which still requires a fresh, reproducible
PASSED complete-verification receipt. If the target advanced after start, the
captured merge is not rewritten; the drift is reported and that later verify
stays authoritative.
Interruption and refusal are recoverable and never destructive. There is no
state file: a later run reads the worktree, the temporary branch, HEAD,
MERGE_HEAD, and the merge parents to see how far the previous run got, so
--continue can resume a merge an interrupted run already committed, or finish
a fast-forward that was all that remained. When something does not match — the
source branch moved independently, the managed path is not a worktree or is on
another branch, a validation problem in the staged resolution — the run refuses
and preserves the worktree, the branch, and every edit; nothing is committed and
nothing is deleted.
--abort discards the attempt: it removes only the managed worktree and
temporary branch through the link-safe cleanup boundary, and only after proving
each is the resource it manages,
refusing while the worktree still holds uncommitted changes rather than throwing
away work. The source and the integration target are left unchanged.
Reconcile is deliberately not an integration engine. It handles exactly one
folder against the current integration target; it never resolves file content
for you, never combines speculative peer folders, never runs an AI adapter, and
never edits a task status. A conflict that appears only between two unaccepted
sources while building a batch candidate is outside this command — that set
still goes through verify --batch's skip decision and then assent rework or
assent reject.
Parallel execution
You can point N terminals at N different work folders, e.g. assent run parallel01, assent run parallel02; or let the scheduler arrange parallel
execution across folder dependencies with assent run --all --jobs N. run --all stays a single foreground terminal and prefixes each subprocess's
messages live as [work folder] message; during parallel execution the
prefix identifies each line's source.
The parent terminal shows the prefixed messages above; the root
.assent/_assent.log only keeps the startup header and per-folder start/
finish/failure scheduling summaries. Each work folder's own _assent.log is
kept by its subprocess with the full raw output, without the parent's
prefix, and is never written twice. Each folder's own tasks and logs
separately use the tNNN_name.e.toml and tNNN_name.r.toml filenames.
Every work folder has its own assent.lock, so only one run at a time is
allowed per folder; Git is always enabled, and every folder always gets its
own worktree at <project name>.worktrees/<folder>/ — this is the
foundation of safe parallel processing.
The lock file is diagnostics, not the lock
assent.lock stays on disk after a run, on purpose. It is a diagnostics
record — the PID, the start time, and the folder name of the last run — and
nothing decides anything from its contents or its existence.
The actual ownership is an OS-level exclusive lock (msvcrt on Windows, fcntl on POSIX) tied to the open file handle for the process's lifetime. Normal exit, Ctrl+C, a crash, and a forced kill all release it automatically, because the OS closes the handle either way. There is therefore no such thing as a stale lock here, no PID-reuse hazard, and no cleanup procedure:
- Do not read the file's presence as "a run is in progress" — it is present after every run that has ever touched the folder.
- Do not delete the file to recover from anything. Deleting it introduces a
race and fixes nothing; the next run reuses it, and
assent archiveeven creates it when it is missing, because a missing lock file proves nobody holds the folder. - If a folder really is busy, the next
assent runsays so when it fails to acquire the lock. That refusal, not the file, is the signal.
The one thing the recorded PID does tell you is about a living process:
assent run --all does not finish its own interrupt path until every work-
folder child it owns has exited and been reaped, on every exit path including
a refusal or a scheduling error. So if the recorded PID is still alive, that
is a genuinely running process to wait for — never a stale-file situation to
clean up by hand.
The limitation worth knowing is unrelated to staleness: flock and
msvcrt.locking semantics are unreliable on some network filesystems, so this
lock only guarantees mutual exclusion on a local filesystem.
The version-control boundary is deliberately simple: AGENTS.md is the
project rules; when tracked, the worktree's branch version is used, and when
not tracked, the prompt supplies the main-tree absolute path. The whole
.assent/ is the assent management plane, excluded via .gitignore and
kept only in the main worktree. The scheduler likewise supplies t/r files and
the default verification script as main-tree absolute paths, and the two
contracts as their ~/.assent absolute paths; the verification script is
loaded from the main tree but its execution cwd is still the worktree. Whenever any .assent/ file has
entered Git, the scheduler fails closed before opening a session, to prevent
the worktree from ending up with a second source of truth.
AI meetings happen in the main tree. From the main tree you can review every
worktree's checkpoints directly with git worktree list, git log <branch>, and git diff main...<branch>, with no need to enter the
worktree directory.
The inherent cost of parallel execution is shared quota, and merging branches back into the main line is a human responsibility.
The usage loop (three acts)
Act 1: planning meeting (interactive session)
Let's start planning. Please read AGENTS.md, ~/.assent/instructions.md, and
~/.assent/format.md, then discuss the following goal with me and progressively
write the consensus into task files under .assent/<work folder>/:
<your goal>
Every consensus reached during the meeting is immediately fixed into a task
file; before adjourning, run assent check — not passing means the meeting
isn't done.
Act 2: unattended execution: assent run, then go to sleep. Each task
session runs only its focused verify. Whether folder completion also builds
a temporary integration candidate and runs the full .assent/verify.py
outside the AI session depends on assent.toml's [verification]
receipt_refresh: the default "manual" leaves that to an explicit
assent verify [--batch] afterward; "auto" runs it at closeout as soon as
every task in the folder is done.
assent verify <FOLDER> refreshes that complete verification receipt with zero
tokens and no AI session; assent verify --batch does the same for every
finished, not-yet-integrated folder as one candidate. Either command's
PASSED/FAILED and fresh/stale state is shown in the report, so a stale
receipt can be refreshed unattended. Direct assent accept <FOLDER> and
selected assent accept A B refuse without their matching fresh PASSED
receipt and never start the verifier; assent accept --all instead uses its
fresh-batch release mode or, when batch evidence is absent/expired, its
intentional sequential verify-then-accept mode.
The packaged .assent/verify.py checks both the candidate working tree and the
committed delta from HEAD to its first parent for leftover conflict markers.
Whitespace-only differences, including line endings, trailing spaces or tabs,
and blank lines at EOF, do not block verification; projects that require a
formatting policy can select or add an explicit formatter check. Fresh
assent init asks which real project test to enable: parallel unittest, pytest,
npm test, Flutter test, or a custom command rendered as argv. The packaged
template keeps all of those project-test examples commented, while the new
project copy activates exactly the selected command. A fresh verifier therefore
fails when the selected test is absent instead of reporting verify: OK from
an empty skeleton.
On repeat initialization, assent init never overwrites an existing verifier
and refuses --test when one is already present. It does replace
~/.assent/format.md and ~/.assent/instructions.md with the packaged
contracts, and merges missing active settings into ~/.assent/assent.toml
without changing existing or custom values. Invalid TOML or input is refused
before any managed file changes. A verifier digest change makes old
receipts stale, so refresh them with assent verify <FOLDER> during unattended
verification before asking a human to accept.
Rerunning verification yourself: a task's focused verify command is
recorded in its tNNN_name.e.toml verify field, and you can run that exact
command yourself from inside that work folder's isolated worktree at
<project>.worktrees/<folder>/. During assent run, the run output echoes
the same text as a verify: <command> line, immediately followed by verify passed (exit 0) or verify failed (exit N), so that printed line is the
literal command to rerun by hand. The complete stage runs assent verify <FOLDER> in a temporary integration candidate at
<project>.integration/target-<uuid>, a sibling of <project>.worktrees/, on
branch assent-integration/<folder>/<uuid>. This is the merged candidate tree
that the complete .assent/verify.py verifies and the receipt certifies; it
exists throughout the entire test run and is removed after the tests finish.
To reproduce or watch that stage manually, use the candidate as the command's
cwd while it exists and run the verifier script from the main worktree, for
example python <main-worktree>/.assent/verify.py; do not run it from the
source worktree as if that were the integration candidate. Cleanup runs in a
finally block, so normal completion, a Python exception, and Ctrl-C clean it
up. Only a hard kill (such as taskkill /F) or power loss can leave residue;
assent has no automatic stale-candidate recovery. Do not manually run a raw
Git worktree-removal command or recursive deletion against residue. Preserve
the exact candidate path and branch as recovery evidence and use the owning
Assent recovery/retry path, which re-proves ownership, inventories directory
links and other directory reparse points, and detaches each link object before
recursive removal. If that proof cannot be completed, the path, branch, and
external target remain in place.
Linked-target cleanup warning: Assent detaches each directory-link object
before any recursive Git or filesystem removal and never traverses its resolved
target. External link targets survive success, refusal, failure, interruption,
and retry. This applies to clean, archive, reject, reconciliation,
setup-failure cleanup, and temporary verification candidates; deleting a link
object is not deleting anything through its resolved path.
Ignored inputs the candidate still needs: git worktree add builds that
candidate from tracked content, so ignored paths are absent from it. Complete
verification therefore mirrors exactly two kinds of artifact from each source
worktree that enters the candidate, at the root or nested below tracked
parents: ignored directory links you provisioned yourself (Windows junctions
and directory symlinks, POSIX directory symlinks), such as a nested
lib/l10n/arb, and ordinary ignored leaf files sitting inside an otherwise
tracked directory, such as a generated lib/models/task.g.dart beside its
tracked source. A directory becomes a link to the same resolved target, a file
becomes a candidate-side link to the source file (a same-volume hard link on
Windows, a file symlink on POSIX). Nothing is copied, and you never prepare
hardlink twins or convert a generated file to a symlink by hand.
Everything else stays out. Whole ignored directory trees are pruned rather
than walked, so .git, .assent, build output, caches, credentials, editor
state, and every path inside a mirrored link's target are never enumerated, and
neither is a file whose parent chain is not part of the candidate's tracked
tree. A destination must be absent from the candidate and ignored there; a
mirrored artifact never replaces or shadows tracked content. When several
folders are verified as one candidate their artifacts are unioned — one path
with one directory target, or one file content digest, is a single artifact,
while conflicting targets, differing file contents, a kind mismatch, an
overlap, a dangling link, or an occupied destination refuses before the
verifier runs and before any PASSED receipt exists. The mirrors last only for
that verifier run and are removed before the temporary worktree is, so your
source worktree's links, generated files, and external targets survive a pass,
a failure, and a Ctrl-C alike. There is no force flag and no project setting
that widens this.
Parallel test execution: choosing unittest during assent init activates
the packaged helper run_unittest_parallel(), which runs each
tests/test_*.py module in its own subprocess concurrently instead of one
process running the whole suite serially. The packaged template leaves this
and the pytest, npm, and Flutter examples commented until one choice is made.
Process isolation is deliberate: unittest modules mutate process-global state
(os.chdir, os.environ), so sharing one interpreter across modules would let
them corrupt each other. Concurrency defaults to min(module count, CPU count);
set ASSENT_VERIFY_JOBS to override it. Selecting a command changes the
generated verifier digest, so it expires existing receipts once; rerun
assent verify <FOLDER> to reissue them.
A worktree is a change-isolation, conflict-management, audit, and recovery
boundary, not a security sandbox. danger-full-access or bypassPermissions
still permits an AI to reach resources available to its OS identity, including
network, credentials, external Git writers, and files outside the worktree.
Use unattended runs only with trusted projects and accounts; Assent does not
provide a container or VM sandbox or intercept those external effects.
Act 3: review meeting (interactive session)
First read _report.md yourself (it is the agenda: progress, BLOCKED
sticking points, checkpoint hashes), then open a session only for the tasks
that need a decision:
Please read .assent/<folder>/t003_xxx.e.toml, t003_xxx.r.toml, and the diff
of the commit auto(<folder>/t003) at <hash>, explain the sticking point, and
propose a fix.
Carrying out the decision means the AI edits the task file (status back to
TODO, added clarification, new tasks, marked SKIP); once assent check
passes, go back to Act 2. DONE remains an execution claim until a human
reviews the report. The verification receipt is scheduler evidence, not
approval. Direct assent accept <FOLDER> quickly rebuilds the candidate and
publishes only when its source tip, integration tree, and verifier digest
exactly reproduce a fresh PASSED receipt; it does not run the full tests.
Selected assent accept A B applies the same no-verifier rule to exactly the
matching batch receipt. assent accept --all is the documented exception: a
fresh batch receipt is replayed atomically, while absent or expired batch
evidence invokes the sequential per-folder verification fallback. Remote
synchronization remains a separate ordinary Git decision, and
assent clean <FOLDER> is the final optional cleanup. When review or
verification of a live, not-yet-accepted folder turns up a correction or a
missing piece of that folder's own objective, prefer appending it to that
folder as a newly numbered task rather than opening a new one; earlier tasks
are never rewritten or renumbered to carry it. Open a new work folder when the
objective is genuinely distinct, when the relevant folder is already accepted,
archived, or rejected, or when dependency or base isolation needs a separate
source lineage; an old folder can keep taking
part in dependency resolution via _folder.toml's after. A folder's
completion is derived from its task files — it is complete only once every
task is DONE/SKIP.
Command reference
The full form of run, status, check, and report is
assent <command> [options] [FOLDER]. FOLDER may be stated explicitly;
when omitted, run derives the single runnable folder from current task
state and _folder.toml's after upstreams, and refuses on ambiguity.
status, check, and report act on all folders when FOLDER is omitted.
--config PATH selects the project-level config file, defaulting to
.assent/assent.toml. That file is the optional override layer on top of
~/.assent/assent.toml, and it also locates the project (the project root is
the parent of the .assent directory the path lives in), so the path is
meaningful even when no such file exists. The config file no longer maintains
a work-folder pointer. --config and FOLDER are orthogonal — use either
alone or together, e.g.
assent status --config configs/night.toml parallel01.
Work-folder names are portable Windows/Git-ref names: non-empty, with no
whitespace, path separators, control characters, Git-ref-forbidden characters
(~, ^, :, ?, *, [), or Windows-forbidden characters (<, >,
", |). They cannot start with - or ., contain .. or @{, end with
. or .lock, or use a reserved Windows device name. The name becomes the
Git branch prefix, so this validation happens before a worktree or branch is
created.
assent verify <FOLDER> is a zero-token, single-folder full-verification
receipt refresh; it never changes the target or opens an AI session.
assent verify A B is the exact selected-batch form: it normalizes A and B to
dependency order, verifies one integration candidate once, and writes one
batch receipt for exactly that set. assent verify <FOLDER> --focus instead
runs distinct DONE-task checks in the source worktree, writes no receipt, and
cannot authorize acceptance.
assent accept <FOLDER> is explicit human approval for one completed folder.
It never runs the full tests: except for an ancestry-proven already-integrated
no-op, it requires a fresh matching PASSED receipt, rebuilds the candidate,
and records a guarded --no-ff merge. assent accept A B requires a fresh
batch receipt for exactly A and B, replays it without verification, and
publishes all selected folders atomically or none. assent accept --all has
the intentional two-mode exception: a fresh PASSED batch receipt is replayed
atomically, while absent or expired batch evidence runs the sequential
per-folder verify-then-accept path. Malformed batch evidence refuses rather
than falling back. Receipts are disposable derived evidence; content changes
make them stale. Direct and selected acceptance never silently expand their
set or start verification. None of these commands connects to a remote, uses
--push, pulls, rebases, force pushes, resolves conflicts, deletes source, or
offers automatic conflict resolution. The integration lock cannot stop
external Git writers; do not run writing Git commands in the same main
worktree during acceptance. Re-running after success is idempotent where the
source is already integrated.
assent clean [FOLDER ...] only deletes worktrees and branches that are fully
merged and clean; when it cannot prove that, it skips the folder. It never
touches .assent/, has no force option, and is unrelated to git clean.
assent reject <FOLDER> is the explicit human-adjudicated rejection action,
kept separate from routine cleanup: it first archives uncommitted changes as
a wip commit, prints each branch's full tip hash as evidence (recoverable by
hash only within git's gc grace period), then force-deletes that folder's
worktree and same-prefix branches, resets DONE/WIP/BLOCKED tasks back to
TODO, and leaves a rejected record with full Git evidence in the r file
(SKIP is not overturned). FOLDER is required and cannot act on all
folders; it refuses while a run is in progress.
assent rework <FOLDER> <TASK> is the non-destructive reopening of a single
task. By default it keeps all code and only resets the target status to
TODO; downstream tasks that have started or completed require an explicit
--cascade to be reverted along with it. --reason TEXT preserves the
adjudication reason. --revert-code is fail-closed: it creates a new
reverse commit only when the target's checkpoints form a contiguous tail of
the current branch, and it never rewrites Git history. On success it
regenerates the report but does not run run automatically; a failed
precheck, status update, or report regeneration all return failure.
Two old settings have been removed: the work folder is no longer maintained by a hand-edited config pointer, and Git has no disable switch or git-less degraded mode; the work folder is stated explicitly on the command line or derived from task-file facts, and Git is always enabled.
| Command and a representative invocation | Options and effect | Token cost |
|---|---|---|
assent run [FOLDER]assent run parallel01 |
Runs a work folder until every task is DONE/BLOCKED/SKIP. Omitting FOLDER derives the single runnable folder; --once stops after the next task; --task ID runs a single task while still checking its upstreams, e.g. assent run --task t003 parallel01. |
Only spent while an AI session runs; --once or --task run at most one task |
assent run A Bassent run A B --allassent run A B ... |
Runs exactly A then B in the stated order and stops on the first failure. With --all, it then runs every remaining incomplete folder in dependency order; the literal ... instead appends every remaining folder as one selection snapshotted before the run starts. ... and --all cannot be combined, and neither form verifies or accepts implicitly. |
Only spent while an AI session runs |
assent run --all --verifyassent run A B --verify |
Runs, then — only if the run exited zero — runs the complete verification that matches the selection: one folder as a folder receipt, an exact multi-folder selection as that selected batch, --all or a bare ... as the whole-project batch. The verification's exit code becomes the command's. With --once or --task it verifies only when that limited run left the single selected folder complete; an incomplete folder fails the request without writing a receipt. |
Only spent while an AI session runs; the verification itself is zero |
assent run --allassent run --all --jobs 2 |
Runs every incomplete folder in _folder.toml dependency order; --jobs N caps how many folders run at once (default 1), with the parent terminal live-tagging each subprocess's output as [folder] message. |
Only spent while an AI session runs |
assent status [FOLDER]assent status parallel01 |
Shows progress statistics, the next task, the branch, and the last checkpoint. Accepts --config PATH. |
Zero |
assent check [FOLDER]assent check --config .assent/assent.toml parallel01 |
Validates task-file format, dependency-cycle freedom, config, and environment; this is the planning meeting's adjournment condition. Accepts --config PATH. |
Zero |
assent report [FOLDER]assent report parallel01 |
Generates and displays the work folder's human-readable report _report.md. Accepts --config PATH. |
Zero |
assent verify <FOLDER>assent verify parallel01 |
Runs the complete verifier once for one folder's temporary integration candidate and refreshes the derived receipt; no target change and no AI session. Report status is PASSED/FAILED, fresh/stale. |
Zero |
assent verify A Bassent verify A ... |
Verifies exactly A and B in dependency order with one integration candidate and one full verifier run, writing one batch receipt for that exact set; --no-bisect applies to a batch only. A selected conflict refuses rather than skipping. A ... expands the selection over the remaining finished folders and is still exact. |
Zero |
assent verify <FOLDER> --focusassent verify parallel01 --focus |
Repeats distinct DONE-task verify commands in the source worktree; writes no receipt and cannot authorize acceptance. | Zero |
assent accept <FOLDER>assent accept parallel01 |
Explicit human approval for one folder. Never runs complete verification; except for an ancestry no-op, it requires a fresh exact PASSED receipt and quickly rebuilds the candidate. |
Zero |
assent accept A Bassent accept A ... |
Explicit human approval for exactly A and B from their matching fresh batch receipt; replays the dependency-ordered chain without verification and publishes all or none. It never expands the set or falls back. A ... selects the remaining finished folders too and still requires evidence for exactly that expanded set. |
Zero |
assent accept --all |
Fresh PASSED batch receipt: atomic replay without new verification. Missing/expired evidence: sequential verify_folder_if_needed then accept in dependency order, stopping on failure while preserving earlier publications. Malformed evidence refuses; already-integrated folders no-op and cleaned sources skip. |
Zero |
assent reconcile <FOLDER>assent reconcile --continue parallel01 |
Prepares one finished folder's source-versus-target conflict in the isolated worktree <project>.reconcile/<FOLDER> so a human can resolve the reported files by hand; --continue stages and validates that resolution, commits the merge, and fast-forwards the source branch; --abort discards only the proven managed worktree and branch. Never changes the target, resolves content, runs focused or complete verification, writes a receipt, or accepts. FOLDER is required; no --all. |
Zero |
assent clean [FOLDER ...]assent clean A Bassent clean A ... |
Cleans up only worktrees and same-folder-prefix branches that are fully merged and clean; skips anything it cannot prove, never touches .assent/, and has no force option. Takes one folder, several, or the literal ... remainder, and acts on all work folders when none is named; several folders are cleaned in one upstream-first pass. |
Zero |
assent archive <FOLDER ...>assent archive --allassent archive --restore FOLDER |
Retires finished folders: contains clean, then compresses the plan into _archive/ and registers it in the roster. Named folders keep single-folder archive's contract — every one is attempted and an ineligible one exits nonzero — while --all skips an ineligible folder without failing. --restore reverses exactly one archive and takes neither --all nor .... |
Zero |
assent reject <FOLDER>assent reject parallel01 |
Human-adjudicated rejection: archives uncommitted changes, removes that folder's worktree through the link-safe cleanup boundary, then force-deletes same-prefix branches (recording full tip hashes before deletion), and resets DONE/WIP/BLOCKED tasks to TODO with Git evidence kept in the r file. FOLDER is required; refuses while a run is in progress. |
Zero |
assent rework <FOLDER> <TASK>assent rework parallel01 t003 --cascade --reason "review rejected" |
Non-destructively reopens a single task; keeps code by default, --cascade states downstream propagation explicitly. --revert-code creates a new reverse commit only when checkpoints form a contiguous tail. Updates the report on success, does not run automatically. Accepts --config PATH. |
Zero |
assent init --test CHOICEassent init --path C:\work\my-project --test pytest |
Installs the user home ~/.assent (shared settings plus the instructions.md and format.md contracts) and the project's .assent/verify.py, AGENTS.md bridge line, and .gitignore entry, after selecting exactly one real project test: parallel unittest, pytest, npm test, Flutter test, or custom argv. Without --test, fresh init shows a numbered menu. Repeat init does not prompt, preserves an existing verifier, refreshes both user-home contracts, and merges only missing active config defaults. An old project copy of a contract is removed only when it matches the packaged text exactly; a project assent.toml is preserved as an override and reported. Invalid input/TOML refuses before any managed file changes. |
Zero |
assent doctorassent doctor |
Diagnoses the machine environment (Python version, git, adapter CLIs, temp directory writability); needs no FOLDER or --config, and runs without an existing .assent/ project. |
Zero |
assent --version |
Prints assent followed by the installed distribution version and exits; works without a project or subcommand. |
Zero |
Each subcommand's -h/--help shows that layer's actual syntax; there is
no top-level --config or other global option that applies to every
subcommand.
Adapters, model tiers, and effort levels
Assent works with different AI CLI tools via pluggable adapters. Each task file
specifies an abstract tier (prime, core, or lite) instead of a concrete
model name; the adapter's configuration table translates that tier into the actual
CLI model for this run. Similarly, a task can request an abstract effort level
(heavy, normal, or slight), which the adapter translates to the vendor's
concrete CLI value (if any).
Supported adapters
Claude (adapter.name = "claude")
[adapter.claude]
command = "claude"
extra_args = ["--permission-mode", "bypassPermissions"]
[adapter.claude.models]
prime = "fable" # Fable 5 – fastest tier
core = "opus" # Opus 4.8 – balanced tier
lite = "sonnet" # Sonnet 5 – efficient tier
Codex (adapter.name = "codex")
[adapter.codex]
command = "codex"
extra_args = ["--sandbox", "danger-full-access"]
[adapter.codex.models]
prime = "gpt-5.6-sol" # largest model
core = "gpt-5.6-terra" # balanced model
lite = "gpt-5.6-luna" # efficient model
Antigravity (adapter.name = "antigravity")
The Antigravity adapter runs Google's Gemini models via agy (Antigravity CLI),
a free locally-installed CLI that requires interactive login once per machine.
This adapter communicates headlessly using print mode (plain-text output, no JSON
events) and includes preflight validation of model/effort combinations before
opening a session.
[adapter.antigravity]
command = "agy"
extra_args = ["--dangerously-skip-permissions"]
[adapter.antigravity.models]
prime = "gemini-3.1-pro" # Gemini 3.1 Pro – highest quality
core = "gemini-3.6-flash" # Gemini 3.6 Flash – balanced (new)
lite = "gemini-3.5-flash" # Gemini 3.5 Flash – efficient
# Antigravity effort translations per tier. The notes below explain each.
[adapter.antigravity.default_effort]
prime = "heavy"
core = "heavy"
lite = "heavy"
# Gemini 3.1 Pro supports only low and high efforts, not medium. For quality,
# the abstract normal effort is translated up to vendor high (never silently downgraded).
[adapter.antigravity.efforts.prime]
normal = "high"
# Gemini 3.5 Flash supports only low and medium, not high. The lite tier's
# abstract heavy effort is translated to vendor medium (the family's ceiling),
# visible here in the config table where it can be inspected and overridden if needed.
[adapter.antigravity.efforts.lite]
heavy = "medium"
Model/effort matrix
Task files specify an abstract tier and optional effort. The adapter translates this into the concrete CLI invocation. The full 9-cell grid below shows what each task-file (tier, effort) pair resolves to in each adapter:
Claude adapter
| Effort | prime (Fable) |
core (Opus) |
lite (Sonnet) |
|---|---|---|---|
| slight | --model fable --effort low |
--model opus --effort low |
--model sonnet --effort low |
| normal | --model fable --effort medium |
--model opus --effort medium |
--model sonnet --effort medium |
| heavy | --model fable --effort high |
--model opus --effort high |
--model sonnet --effort high |
Codex adapter
| Effort | prime (gpt-5.6-sol) |
core (gpt-5.6-terra) |
lite (gpt-5.6-luna) |
|---|---|---|---|
| slight | --model gpt-5.6-sol --effort low |
--model gpt-5.6-terra --effort low |
--model gpt-5.6-luna --effort low |
| normal | --model gpt-5.6-sol --effort medium |
--model gpt-5.6-terra --effort medium |
--model gpt-5.6-luna --effort medium |
| heavy | --model gpt-5.6-sol --effort high |
--model gpt-5.6-terra --effort high |
--model gpt-5.6-luna --effort high |
Antigravity adapter (1.1.5+)
| Effort | prime (3.1 Pro) |
core (3.6 Flash) |
lite (3.5 Flash) |
|---|---|---|---|
| slight | --model gemini-3.1-pro --effort low |
--model gemini-3.6-flash --effort low |
--model gemini-3.5-flash --effort low |
| normal | --model gemini-3.1-pro --effort high |
--model gemini-3.6-flash --effort medium |
--model gemini-3.5-flash --effort medium |
| heavy | --model gemini-3.1-pro --effort high |
--model gemini-3.6-flash --effort high |
--model gemini-3.5-flash --effort medium |
Notes:
- Antigravity prime/normal: Gemini 3.1 Pro does not support
medium, so assent chooseshighinstead (quality-first mapping). This is not a silent fallback—the configuration table makes it visible and auditable. - Antigravity lite/heavy: Gemini 3.5 Flash has no
higheffort level, sohighis translated tomedium, the family's maximum available. - Antigravity 1.1.5 minimum: This is the version that supports
--effort, stable model slugs, and the headless fixes required for unattended execution. Earlier versions are rejected before opening a session.
Using Antigravity adapter
First-time setup
- Install
agy(Antigravity CLI) on your machine if not already present. - Run
agy auth loginto interactively sign in once per machine. - Verify your installation with
agy --version(must be 1.1.5 or later) andagy models(shows available models).
Assent will not modify your ~/.gemini/antigravity-cli/settings.json, run
the login browser, or interact with credentials. Your login credentials and
workspace trust remain under your control.
Example task file using Antigravity
title = "Analyze code with high-quality reasoning"
model = "prime"
effort = "heavy"
status = "TODO"
scope = ["src/", "tests/"]
verify = "python -m pytest"
goal = "Use Gemini 3.1 Pro (highest quality) to review the codebase."
When assent run executes this task, it will:
- Validate that Antigravity 1.1.5+ is installed and can reach
gemini-3.1-pro --effort high. - Open a headless session with
agy --print --model gemini-3.1-pro --effort high --mode accept-edits .... - Run the verification command and record the result.
Switching adapters in an existing project
Changing [adapter] name is a one-line config change. Existing task files do
not need to change; they still use model = "prime" and effort = "heavy", and
the new adapter's configuration table translates those the same way. Once you
have switched adapters, the next assent check will validate the new adapter
before any session starts.
Configuring model and effort translations
The settings in ~/.assent/assent.toml show how to customize the
tier-to-model mapping and the abstract-to-CLI effort translations. The lookup
order is always:
- Task file's explicit
effortannotation (if present) - The configured
default_effortoverride for this tier (if present) - The built-in default for this tier
A stated [adapter.<name>.default_effort] table overrides per tier; it does
not replace the built-in table. An absent, empty, or partial table therefore
still leaves every tier with a value — write only lite, and prime/core keep
their built-in defaults. The result is that every supported invocation passes a
concrete effort to the CLI; assent never omits the flag and inherits the
vendor's own default.
And for effort translation:
- Tier-specific section:
[adapter.<name>.efforts.<tier>] - Flat section:
[adapter.<name>.efforts] - Built-in baseline:
heavy→high,normal→medium,slight→low(each abstract key falls back independently when a higher-priority table lacks that key).
Example: if your Antigravity setup has a newer 3.1 Pro that supports medium, you can remove the quality-first mapping:
# Remove this line:
# [adapter.antigravity.efforts.prime]
# normal = "high"
# Or set it to the actual value:
[adapter.antigravity.efforts.prime]
normal = "medium"
Reading the session line
When run opens a session it prints one compact line stating the whole
resolved identity:
Session: codex | core->gpt-5.6-terra | heavy->high
Read it as adapter, then two mappings. Each arrow points from the portable
abstract value the task file states, on the left, to the actual argument sent
to that adapter's CLI, on the right — so core->gpt-5.6-terra is the --model
value and heavy->high is the --effort value for this run. All four audit
facts (adapter, tier, model, effort) are on the line; it stays a single line
and is not expanded back into verbose labels.
Configuring Antigravity print timeout
Antigravity's --print-timeout is independent of Assent's watchdog timeout.
The print timeout limits how long the CLI will wait for a single print
invocation to complete; the watchdog limits how long Assent will wait for any
output before killing the session.
In ~/.assent/assent.toml (or a project override):
[adapter.antigravity]
print_timeout_minutes = 120 # AGY will wait up to 2 hours for an answer
Do not set this lower than your longest expected task; assent check will
validate that the print timeout is positive.
Troubleshooting Antigravity configuration
Problem: preflight failed: invalid model selection
Antigravity rejected the model/effort combination during preflight. Check:
agy models # See what models are available
agy --print --model <MODEL> ... # Test your model/effort choice
Common causes:
- Unmapped model tier: add the model to
[adapter.antigravity.models]. - Unsupported effort: the model does not support that effort level. For
example, Gemini 3.1 Pro does not support
medium. Fix the mapping in[adapter.antigravity.efforts.prime].
Problem: authentication required or permission denied
You must have logged in once on this machine:
agy auth login # Opens a browser for Google sign-in
If you are running assent run unattended (e.g., at night), your login must
complete before the run starts. Assent cannot open a browser, log you in, or
detect when you are away; it only uses your existing login credentials.
Problem: command not found: agy
Antigravity CLI is not installed or not on your PATH. Visit the Antigravity
CLI installation docs and verify
with agy --version.
Problem: Quota exhausted mid-task
When Antigravity reaches quota limits, assent run records a WIP checkpoint
with the partial results. When your quota resets (Google typically resets daily
or hourly depending on your plan), you can resume the same task:
assent run <FOLDER> # Resumes from WIP automatically
The task journal records the exact quota-reset time (if available) and the
scheduler will poll until then before retrying. If you need to run a different
folder in the meantime, you can run it in a second terminal as long as it does
not depend on the quota-limited folder.
When [adapter].name is a list, quota exhaustion rotates to the next adapter
in order; the scheduler waits for the rotation poll only after every adapter
in the rotation is exhausted.
Fixing configuration after a preflight error
Do not modify the task file's abstract tier or effort. Instead, update only the adapter configuration. For example, if prime/normal is mapped to high but you want to change it:
# Before
[adapter.antigravity.efforts.prime]
normal = "high"
# After (if normal is now supported)
[adapter.antigravity.efforts.prime]
normal = "medium"
After fixing the config, no changes to the .assent/ management files are
needed; assent check will re-validate and assent run will retry.
Plan format and config files
- Full format contract: assent/templates/format.md
— installed to
~/.assent/format.mdand refreshed there on every successfulassent init. - Working-instructions template: assent/templates/instructions.md
— assent session behavior and cross-project common rules; installed to
~/.assent/instructions.mdand refreshed there on every successfulassent init; project rules stay inAGENTS.md. - Config template: assent/templates/assent.toml
— adapter selection, the abstract tier (prime/core/lite) mapping table,
abstract effort (heavy/normal/slight) defaults and CLI-value translation,
watchdog, and retry parameters. It seeds
~/.assent/assent.tomlon first init; a later init adds only missing active table/key paths there and preserves existing and custom values.
These three are the tool's own files, so each exists once per machine. A
project holds only its AGENTS.md, its .assent/verify.py, its work folders,
and — only where an older layout or a deliberate decision put one there — a
project .assent/assent.toml override.
Tasks that use project media
An image, PDF, audio file, or other media a task needs is ordinary project
context, so the plan schema stays unchanged — there is no inputs, image,
audio, or video field, and assent never attaches a file to an adapter or infers
what media a model can read.
- Name an existing media file by its project-relative path, with its purpose,
in the task's
behaviorornotes. A read-only reference path does not need to enterscope. - Every media file the task may create or modify must be covered by
scope. - Prefer versioned worktree files so the run is reproducible; do not put source
media in the generated
.assent/management plane. verifykeeps the objective checks; visual or perceptual judgment stays a human call ataccept, not a second review state.
The format contract carries a worked example.
FAQ
Q: Does status / check / report consume tokens? No. Only sessions that run an AI consume tokens; the scheduler never feeds any file content to a model — the executing AI reads task files with its own tools.
Q: What if I lose power or crash midway?
Inspect the isolated worktree first. If the interruption was handled and the
task was left at WIP, assent run resumes it with a "continue" prompt. An
abrupt failure can leave uncommitted changes; in that case the scheduler
refuses the dirty worktree instead of guessing, so review and checkpoint the
changes before rerunning. The assent.lock file you find in the folder is not
part of that recovery: the OS released the real lock when the process died,
and the file is only the last run's diagnostics. Leave it alone.
Q: What if the executing AI edits its task file to loosen its own review?
Three layers of defense: the scope exemption covers only its own
tNNN_name.e.toml task file and tNNN_name.r.toml log; any field of the
task file other than status being changed fails the review (compared field
by field against the checkpoint version); check validates deps integrity
and cycle-freedom every round.
Q: Does a BLOCKED task block all progress?
Only tasks that depend on it as an upstream; other tasks continue as usual.
_report.md lists every sticking point and its last log entry.
Q: How do I plug in an AI CLI other than Claude / Codex?
Subclass Adapter and implement a two-step interface.
resolve_model(model: str) -> str first translates the task file's abstract
tier into this run's actual --model value, requested_model; the engine
then translates the abstract effort into requested_effort per the config
file, and calls the existing run_task(prompt, requested_model, requested_effort, cwd) -> TaskResult. An adapter does not define a separate
effort-translation method — it only uses the actual CLI value it is handed.
TaskResult carries exit_code, output, quota_exhausted, and
reset_at; quota detection is encapsulated inside the adapter, and the main
loop is unaware of vendor differences.
Project status
The core is complete: the TOML task/log format, eight subcommands, the claude and codex adapters, and a full unittest suite (runs with no network and no real CLI). Design consensus is recorded in docs/CONSENSUS.md.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file assent-0.1.0a2.tar.gz.
File metadata
- Download URL: assent-0.1.0a2.tar.gz
- Upload date:
- Size: 462.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c8e704eb31a1e1d36039be4ba71dc257e338a0bbe052ce81f3c25aac0f628812
|
|
| MD5 |
17dfaa78ac3bdf142c7f5eb99bdc70a5
|
|
| BLAKE2b-256 |
d0f8e2701c2526304bfa2376034d61855c4bcb20cb1e3985b171c6149bc249a9
|
Provenance
The following attestation bundles were made for assent-0.1.0a2.tar.gz:
Publisher:
publish.yml on edward9s/assent
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
assent-0.1.0a2.tar.gz -
Subject digest:
c8e704eb31a1e1d36039be4ba71dc257e338a0bbe052ce81f3c25aac0f628812 - Sigstore transparency entry: 2290201500
- Sigstore integration time:
-
Permalink:
edward9s/assent@3cc04cb3e6b42d02516c580181fd8ffb5ebf3182 -
Branch / Tag:
refs/tags/v0.1.0a2 - Owner: https://github.com/edward9s
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3cc04cb3e6b42d02516c580181fd8ffb5ebf3182 -
Trigger Event:
release
-
Statement type:
File details
Details for the file assent-0.1.0a2-py3-none-any.whl.
File metadata
- Download URL: assent-0.1.0a2-py3-none-any.whl
- Upload date:
- Size: 261.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0da34d4be1b30a647b20a8ccf88c946f66c7fee46f376edb7fff87c132304872
|
|
| MD5 |
1f54b0297ed9fcc1911403633df0d585
|
|
| BLAKE2b-256 |
f64a6fef8b44ea680ee4cb8445fadaae63811bd1f6b6c4533b306fc16c95f5cc
|
Provenance
The following attestation bundles were made for assent-0.1.0a2-py3-none-any.whl:
Publisher:
publish.yml on edward9s/assent
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
assent-0.1.0a2-py3-none-any.whl -
Subject digest:
0da34d4be1b30a647b20a8ccf88c946f66c7fee46f376edb7fff87c132304872 - Sigstore transparency entry: 2290201518
- Sigstore integration time:
-
Permalink:
edward9s/assent@3cc04cb3e6b42d02516c580181fd8ffb5ebf3182 -
Branch / Tag:
refs/tags/v0.1.0a2 - Owner: https://github.com/edward9s
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3cc04cb3e6b42d02516c580181fd8ffb5ebf3182 -
Trigger Event:
release
-
Statement type: