tickmarkr
v2.11.0
Published
Spec in, verified work out.
Maintainers
Readme
▄▖
▄█▀
▗▄ ▄█▀ tickmarkr
▝█▛ spec in, verified work out.tickmarkr
Assertions are free. Tickmarks are earned.
tickmarkr is a spec-driven orchestration harness for AI coding agent CLIs. You write a spec with
acceptance criteria; the engine routes tasks to the best installed agent CLI (claude-code, codex,
cursor-agent, opencode, grok, pi, kimi) by cost and capability, dispatches work in git worktrees for
change isolation — as interactive TUIs when running under herdr, headless
subprocesses otherwise, or in Orca terminals when auto detects both Orca
markers (name that driver explicitly outside one) — and independently verifies each committed
result by checking for no new
baseline failures per task, then strictly verifying the integration tip. Green tasks consolidate onto a
tickmarkr/<runId> branch; merging to your mainline is always your call, never automated. Engage
with full visibility into routing decisions, worker progress, and gate verdicts — or run headless
and review the local execution log afterward.
Here, isolated git worktrees means change isolation: each task gets its own worktree and branch so its diff is separated from sibling tasks. It is not a process sandbox or host-containment boundary.
Invariants
These are law; the codebase fails closed around them:
acceptance[]required on every task; compile fails without it- new engagements consolidate to a
tickmarkr/<runId>branch — never main - gates never trust worker claims; each task is checked for no new baseline failures and the merged integration tip is strictly re-verified
- state is files + git only; no DB, no services
- worker/judge/review/consult prompts end with machine-parseable trailers (structured JSON verdicts); parse defensively, fail closed
Any agent (or human) operating this repo must respect the same five rules above — they are not merely internal implementation details, they are the contract the gates enforce.
Install
npm i -g tickmarkrThis installs two identical bins: tickmarkr and its short alias tkr — use whichever you
prefer, every example below works with either.
Requirements:
- macOS or Linux. Every command shells out through
bash, so native Windows is not supported — use WSL (untested). - Node ≥ 20.3 and
giton PATH. - At least one agent CLI on PATH (
claude,codex,cursor-agent,opencode,grok,pi, orkimi), authenticated through its own login. tickmarkr never handles vendor API keys itself.
Then verify your fleet:
tickmarkr doctor # probes installed adapters, herdr, auth; prints the capability matrixExpect doctor (and init, which runs it) to take a while on first run: auth detection is
one real, short LLM call per configured model per installed CLI — honest, but not instant. A
machine with three CLIs and several models each can take a minute or more; each probe is capped
at 60s.
doctor also sweeps a catalog of known agent CLIs that have no adapter yet and prints an
advisory detected — no adapter; not routable row for any found on PATH — a freshly installed
harness is visible the day it lands, and never routed until an adapter ships for it.
Quickstart (5 minutes)
tickmarkr init # guided setup + doctor; scaffolds config and spec template
# edit tickmarkr.spec.md # write your tasks and acceptance criteria
tickmarkr compile tickmarkr.spec.md # spec → task graph (fails without acceptance criteria)
tickmarkr plan # dry-run routing decisions + cost estimate
tickmarkr run # execute, route to best CLI, gate every result (--concurrency N)
tickmarkr report <runId> --md # Markdown on stdout; redirect to save beside the specThat's the flow: init scaffolds config, you write tasks with acceptance[] criteria, compile
validates and builds the graph, plan shows routing decisions and cost, run dispatches work to
installed CLIs and checks each task for no new baseline failures before strictly verifying the
integration tip, and report documents the outcome. All
green tasks land on tickmarkr/<runId> — merge to your mainline is your decision.
Agent-ready repos: tickmarkr init --agent
tickmarkr init --agent composes with the base init above and additionally installs the
consumer-facing skills (tickmarkr-loop, tickmarkr-auto, tickmarkr-overseer) that ship in the npm tarball, so a
coding agent working in your repo knows how to drive tickmarkr without reading its source:
tickmarkr init --agent # installs host skills, offers CLAUDE.md/AGENTS.md notes
tickmarkr init --agent --force # also overwrite skill files that already exist
tickmarkr init --agent --docs # also append the agent-docs section without an interactive promptConsent rules (queue row 244; D-2141; D-2152):
- an existing currently shipped host skill file (
tickmarkr-loop,tickmarkr-autoortickmarkr-overseer) is left untouched unless you answer yes to a per-file prompt, or pass--force --agentremoves retired skill directories only when their complete, nonempty tree, including a regularSKILL.mdand no extra directory, equals one complete shipped snapshot path for path and byte for byte. This shipped-bytes proof works with or without the ownership marker; a marker alone never proves ownership. Inside today's skills it also removes retired files whose bytes were shipped at that same skill path. A complete earlier snapshot stays intact when its refresh is skipped; its retired files are removed only after a forced or consented refresh is applied. Each deletion saysremoved leftover <path>.--removesweeps those proven retired files before removing skill directories with the same complete-snapshot proof at both host locations. Unproven content is kept byte for byte and reported by path under reserved names or with the exactmetadata.installed-by: tickmarkrmarker; unmarked user skills outside reserved names stay untouched and unlisted. Today's reserved names keep the install and--forcerefresh behavior above; refresh preserves added files.- the no-follow boundary covers linked host roots and their ancestors within the consumer,
skill entries, files and nested directories, including dangling links. Nothing is read, copied,
written or deleted through them; each is named as
kept <path> (symbolic link).tickmarkr version --checknames everyleftover <path>andunproven <path>, including when leftovers are the only installed guidance, and exits non-zero with a remedy naming each path. Move an added file out of the skill directory; modified or missing shipped files usetickmarkr init --agent --force. - a short "tickmarkr" block contains only the five invariants, the version check stop, the one-line
green rule and the routing line naming the loop, auto and overseer skills. It is appended to each
applicable host's guidance (
AGENTS.md, plusCLAUDE.mdwhen the repo uses Claude) only after you say yes, or pass--docs; it is wrapped in<!-- tickmarkr:agent-docs begin/end -->markers and never inserted twice. The loop and auto skills hold the commands, role check, preflight, seat hygiene, verified handoffs, full green rule and spec-template pointer; both point every host to the overseer skill's PROJECT MEMORY duty under Setup (queue row 241; D-2141; D-2152) - declining a prompt still lets the rest of
initcomplete - non-interactive shells (no TTY) never prompt: missing skills are installed, eligible proven leftovers
are removed, and existing shipped files needing overwrite consent are skipped with their complete
earlier snapshots intact; use
--forceto refresh them. The summary names what was removed, kept or skipped and which flag would enable a refresh
Monitor and supervise
tickmarkr ui [runId] opens one cockpit with 1 Home, 4 Run, 5 Evidence. Without a
run ID it selects the latest journal; an empty repository opens Home. Use
tickmarkr ui <runId> --view run or --view evidence to open a delivered view directly.
tickmarkr ui --setup <runId> opens Run Parks and preserves the requested run identity.
Fleet/Bootstrap and Plan/Health are explicit follow-ons: keys 2/3/6 are not installed.
Use the existing tickmarkr fleet, tickmarkr init, tickmarkr plan and tickmarkr doctor
commands for those workflows.
? opens the shortcut sheet; Tab/Shift-Tab move focus through visible regions, Enter opens
selection, and Esc closes the deepest overlay. q quits; text-entry mode keeps q1?
literal. Run shows every task in the matching graph, its recorded attempt/path/pane/alarm,
and current-attempt gate evidence. o requests focus of the recorded owned pane when the
driver supports it; unavailable panes leave an evidence diagnostic. Evidence keeps original
journal #L identities, full verdict paging, and a stable selection with Follow off.
tickmarkr status <runId> # preserved printed engagement state
tickmarkr status <runId> --oneline # compact snapshot, then exit
tickmarkr status <runId> --watch # TTY: Run cockpit; non-TTY: line output
tickmarkr status <runId> --watch --plain # preserved line/ANSI fallback, including on a TTY
tickmarkr resume <runId> # continue an engagement from the local execution log
tickmarkr approve <runId> <taskId> --park <line>@<ts> # decide the park status printed; see below
tickmarkr report <runId> # cost/quality report
tickmarkr report <runId> --md > feature.record.md # explicit file write beside your spec
tickmarkr profile # show the learned routing profile
tickmarkr profile --explain <shape> <channel> # why a channel ranks where it does for a shapeFor machines, tickmarkr status <runId> --watch --events replays and follows projected
decision events as one JSON document per stdout line. --jsonl and --decision-events
are aliases; keep stderr keepalives separate (never 2>&1). This projection is distinct
from raw journal.jsonl. Webhook delivery remains opt-in via --webhook <url>.
Preserved printed twins also include separate fleet --print and fleet --why outputs,
plan, doctor, report --compare <baseline-runId>, report --bundle <path> (an explicit
proof-bundle write), and stats (all runs, no run ID). Report retains its learning preview
and comparison warnings; absent metering stays “not measurable,” never an invented $0.
A manual cockpit keeps its final receipt at run-end and follows a later resume of that run.
By default, the daemon-owned board requests graceful shutdown at run-end and closes only its owned pane
after checking its watch presence stood down; unconfirmed cleanup is reported. The existing
visibility.keepPanes: forever debug override preserves panes. Herdr keeps
task/gate grouping, short titles, and the board beside its caller without taking focus.
Quitting or orderly signals restore raw mode, pointer tracking, title and alternate screen,
and release only that observer's presence. Green tasks land on tickmarkr/<runId>;
merge to your mainline is always your call.
Escalation and consults
When gates or tasks stall, tickmarkr escalates to a frontier-model consult for a structured verdict.
The consult can recommend rerouting (exclude a failed channel/adapter and try the next ranked option)
or human approval if deadlock persists. Exclusions persist across resume — a task that escalated
away from a failing adapter will never retry it in subsequent tickmarkr resume calls.
Approving tasks
The recorded partial-human-park case has 1/3 merged, human T2, blocked T3, and a
passed tip verify. It is PARTIAL, not green: T2's humanGate: true parks it before
dispatch. In Run (4, or tickmarkr ui --setup <runId>), select T2, press a for
Actions, choose Approve with Enter, and review the confirmation: run/task, original park
#L, actor/reason, exact tickmarkr approve argv, append-only consequence and enactor.
Only y confirms; n/Esc cancel, and Enter never confirms.
Read the receipt's newly appended task-approved line, actor/reason and disposition back
from the journal. Approval records permission; it does not dispatch work, pass a gate or
mark the task done. With no live owner the receipt says approved; resume required.
Exit the observer and run tickmarkr resume <runId> explicitly. A matching live daemon
can enact the release at its next task boundary; a different live run must end before this
one resumes. Keep any resume refusal and its remediation visible, including a deny/prefer
config conflict; repair the source/config as directed, never edit the compiled graph to
force success. After resume, CURRENT TIP is PENDING until fresh evidence arrives. Completion
requires the latest run-end, a nonfailed known tip result, empty failed, human,
blocked and pending buckets, and owed checks outstanding empty AND known in CURRENT
tickmarkr status <runId> (outstanding 0); the completed case has 3/3 recorded merges.
Empty buckets alone are not completion (D-660): 3/3 merged with every bucket empty and the
tip passed, after the operator waived T2's review, leaves one accepted-risk review check owed.
run/resume exit 0 on execution alone, their final line says
execution complete; outstanding 1 (T2 review) rather than verified, and status reads
outstanding 1. That run completes only when tickmarkr verify --record <runId> discharges
the check and CURRENT status reads outstanding 0 — a discharge after run-end counts, while the
run-end record keeps its old count; outstanding unknown never completes. An unrelated
graph says “not comparable” and supplies no borrowed denominator. Historical GATES RAN
does not establish current completion.
The CLI twin for the same decision is
tickmarkr approve <runId> T2 --park <line>@<ts> --by operator --reason 'ready to proceed',
followed by the receipt check and explicit resume above. Every decision is bound to one park:
status, the park notice and the Run confirmation print its <line>@<ts> token (the park row's
physical journal line and timestamp), and --park names it — without --park the park open
when the command starts is bound. A waive is also bound to that park's failed gate, named
explicitly with --gate (tickmarkr approve <runId> T2 --waive --park <line>@<ts> --gate review).
A decision queued behind another approval is revalidated under serialization, and the daemon
revalidates it again before enactment: once a newer park opens, a stale token, an unbound row or a
mismatched gate is refused (the daemon journals approval-refused) and never waives the newer gate.
A failed task's recheck keeps working with its own bound token: status prints
failed — T3 — failure <line>@<ts>, and
tickmarkr approve <runId> T3 --recheck --park <line>@<ts> re-gates its landed commits with no
worker. Other parks have different permitted decisions:
| Park | Decision and effect |
|---|---|
| Human gate / other non-gate park | Plain approve records permission to dispatch. |
| Attempt cap | Plain approve grants a fresh attempt budget; prior routing exclusions remain. |
| Infrastructure | Plain approve or --recheck; recheck reruns the declared battery and satisfies no gate. |
| Stall with a recorded reapFailure (the worker's process census was unreadable or had survivors) | --recheck bound to that park's token re-verifies the parked attempt's owned census: only an explicitly recorded empty survivors array ([]) releases its harvested commits to the declared battery, with no worker; a missing, unreadable or surviving census re-parks the stall (new token, still approve/recheck). Plain approve dispatches a worker. |
| Ordinary stall (no reapFailure) | Plain approve only — it dispatches a worker; --recheck refuses. |
| Failed review gate | --waive satisfies only that identified gate; --uphold funds one fixed attempt carrying findings; --recheck reruns the battery. |
| Other failed gate | --waive or --recheck; plain approve refuses. |
| Tombstone / gate failure without identifying evidence | Diagnostic only; no invented decision. |
Decisions are append-only and cannot be undone. Unknown tasks, duplicate decisions and changed parks refuse; no success receipt is claimed without reading back the append.
Help and recovery
tickmarkr ui --help, tickmarkr eval --help, tickmarkr unlock --help and
tickmarkr profile reset --help print guidance without opening a UI, seeding fixtures,
unlocking or resetting history. Help flags are recognized before --; arguments after
it are literal data. Help examples describe operations; printing them never executes them.
Recovery remains explicit: unlock <runId> targets a matching provably dead lock;
unlock --garbage handles malformed lock bytes without inventing a run ID. Both require
confirmation (--yes for non-TTY) and refuse live, inaccessible or changed holders.
doctor --cached reads cached diagnostics; doctor --probe-preflight discloses probe
counts/files; doctor --fix-only repairs locally without model probes or catalog refresh.
Default doctor and --fix still probe. doctor --refresh-catalog refreshes only the catalog.
scope <intent-file> --preview is a local, non-writing preview; authoring requires TTY
confirmation or --yes and can make model calls.
Choosing your fleet: tickmarkr fleet
Configure which agent CLIs and models tickmarkr may route before your first run:
tickmarkr doctor # probe auth + capabilities (run after install or credential changes)
tickmarkr fleet # interactive fleet browser (requires a TTY) — review diff to write
tickmarkr fleet --print # effective fleet state (repo > global > defaults), non-interactive
tickmarkr plan # lint the resolved routing table against your spec
tickmarkr run # dispatch with the fleet you confirmedtickmarkr doctor probes and records health; tickmarkr fleet edits routing config. The browser is one
two-pane surface: the left rail lists views (All models, Shapes, Steering) and every
installed agent CLI with its auth state and model count; the right pane is a searchable model list
with tier, context, price, and probe-latency columns. Space allows/denies, Enter classifies an
unclassified model (with a required benchmark-provenance note) or pins a classified one to a shape,
m opens the routing-mode presets, and w renders the unified diff of the one overlay the save
lands in, named in the review: an allow/deny membership edit goes to the layer that holds it (the
repo overlay when it declares that family, else your user overlay), everything else to the user
overlay; a batch needing both is refused — save each half in its own session. Nothing is written
until you confirm with y. A worker-only deny on a partially excluded adapter takes two rail
presses: out · all seats, then out · workers. The Shapes view pins from a candidate picker ranked by
the production router.
Routing-mode semantics, pin/floor/prefer precedence, review and consult steering syntax,
provenance rules, and --quality / --mode flags are documented in
FLEET.md (advanced reference).
Steering
The fleet browser's Steering view sets review.prefer and consult.prefer; routing modes
(risk-based, partner-led, staff-led) are applied from the presets overlay (m). Full
grammar — including when review
prefer may name a bare adapter versus adapter:model, and why consult prefer entries
require adapter:model form — plus tickmarkr run --supersedes rerun control, is in
FLEET.md.
Model scoping and auth detection
Each agent CLI exposes a list of available models. tickmarkr's routing works only with classified models — those you explicitly enter into the config under tiers. The tickmarkr doctor command probes these models to detect auth status and records the results, which routing consumes to avoid 401/403 dispatch failures.
Model terminology:
- Listed models: All models a CLI advertises as available (e.g., pi advertises both
zai/glm-5.2andanthropic/claude-opus-4-8, but not all are authed) - Scoped models: Listed models with an explicit provider prefix (e.g.,
zai/glm-5.2,zai-coding-plan/glm-5.2). The design rule is to classify scoped models primarily, not unscoped listed models - Classified models: Models you've entered into
tiers— the routing-eligible set, regardless of scope
When you run tickmarkr doctor, it:
- Probes each classified model exactly once (one minimal headless API call per model per adapter)
- Records results locally:
authed: trueorauthed: falsewith the failure reason and probe timestamp - Prints a model-status table for classified models only, showing tier, auth verdict, denial status, and prefer rank
Routing and auth:
- Routing discovers channels only from authed classified models (unauthed channels are dropped)
tickmarkr planlints each excluded model, naming the probe reason and timestamp- If a task's floor can only be satisfied by unauthed models, the plan fails loudly — never a silent fallback
When to re-run doctor:
- After classifying new scoped models into tiers (e.g., onboarding a new vendor or aggregator CLI)
- Before a run, if credentials may have changed (API key rotation, quota reset, subscription renewal)
Operator model control — deny.models: You can bench a classified model without removing it from tiers:
routing:
deny:
models:
- pi:zai/glm-5.2 # stays in tiers, never routes (key is adapter:model)
- cursor-agent:composer-2.5 # reason in git committickmarkr plan lints denied models identically to unauthed ones. Re-enable by deleting one line.
Run output and local execution log
When you execute tickmarkr run, the daemon records every event in an append-only local journal:
task dispatch, gate verdicts, worker status, and merges. Narration streams to stdout; you can also
--watch or tail the journal directly. If interrupted, tickmarkr resume <runId> replays the journal
and continues from the last stable state — deterministic, not ephemeral.
Usage and cost
Every engagement record includes a Usage & efficiency section showing token/window consumption, cost estimates, and first-attempt success rate. Cost reporting follows strict honesty rules and never infers absent data:
Cost model — two channel economics
API channels (real marginal cost):
- Formula:
tokens × price - Pricing data sourced from LiteLLM model prices
- Each estimate carries its basis: input tokens, output tokens, cache reads (if applicable), rate per Mtok, and rate date
Subscription channels (flat monthly, no marginal token cost):
- Two metrics reported when computable:
- Amortized window cost:
plan monthly cost ÷ usable windows per month— a range accounting for time-varying quotas - API-equivalent counterfactual:
metered tokens × API price of the same/nearest model— e.g., a $100/month Claude Max user who consumes tokens worth $500 API-equivalent has paid $500 API-equivalent for flat $100
- Amortized window cost:
- Both metrics appear in the report alongside their sample count and range bounds
Honesty rules (no guessing allowed)
- Ranges, never single numbers: quota multipliers and monthly-window variation make subscription costs a range, not a point estimate
- "Not measurable" never becomes $0: if a channel lacks pricing or metering data, the report explicitly states "not measurable"
- Basis always shown: every cost figure prints the token count, rate used, and date so estimates can be audited
- No network calls: pricing config is operator-maintained locally, seeded with dated comments and LiteLLM's JSON file named as the copy-from source; tickmarkr never calls home to fetch rates
- Attribution from journal: token counts come from tickmarkr's own telemetry spans in the local journal, never from provider invoices or dashboards
Visibility: optional supervised workspace
When running under herdr, tickmarkr creates a labeled pane-and-tab workspace
for real-time visibility (optional — omit --driver herdr or run headless if preferred).
Orca: a detected-or-named execution surface
Orca is the third execution surface. auto resolves herdr first when
HERDR_ENV=1, then Orca only when both Orca-authored markers TERM_PROGRAM=Orca and
ORCA_TERMINAL_HANDLE are present, then subprocess. This environment-only choice executes no
binary or runtime probe. Outside an Orca terminal, name it explicitly with --driver orca or
driver: orca in config. Once selected either way, an unreachable Orca stays a loud Orca driver
failure and is never silently replaced by a hidden subprocess worker.
What Orca supplies is terminals. What tickmarkr keeps is everything that decides whether work
ships: it creates and owns the git worktree for every task (Orca is told which checkout to bind
its terminal to, and never makes one), it runs the full gate battery — build, test, lint,
evidence, scope, acceptance, review — against the commits that land there, and it holds merge
authority, consolidating only green tasks onto the run's tickmarkr/<runId> integration branch.
Orca is given no say over any of the three. Merging that branch to your mainline remains your call,
exactly as with every other driver.
tickmarkr borrows audit-firm vocabulary for its roles: you are the Partner (final sign-off), workers are the field team, the acceptance judge is the EQR (engagement quality reviewer), and the frontier-model consult is the National Office. The terms below use that vocabulary:
Tab vocabulary
<taskId>: one tab per TASK. A task's worker pane and every gate pane it earns — judge, review, consult — live together in that task's tab, so everything happening to one task is in one place. The tab header shows the task token plus one state glyph:- ↻ — the member is a retry attempt (attempt > 0)
- ✋ — the driver detected the member is blocked
- (bare token) — member is running normally
- cleanup · : overflow/teardown generation tabs. When a new generation starts (on retry escalation), a new cleanup tab opens labeled with the newest live member's task ID; it auto-closes when the generation completes
- watch: a single owned pane running
tickmarkr ui <runId> --view run— the same Run cockpit opened by TTYstatus --watch
Pane naming (when visibility.llm = pane)
- judge · : the EQR evaluating acceptance criteria
- review · : the cross-vendor code review gate
- consult · : National Office escalation on deadlock (gates or worker stall)
Notification tiers
Only operator-decision events notify (with sound):
- ✓ Gate failure escalations (move to next channel, human approval needed)
- ✓ National Office verdicts (deadlock resolved or escalation recommended)
- ✓ Human gates (task parked, awaiting
tickmarkr approve) - ✓ Quota failover (channel exhausted, routed to next)
- ✓ Engagement end (unqualified or qualified opinion)
Routine events do NOT notify:
- ✗ Task dispatch (tab already shows progress)
- ✗ Task done (tab state updated, already visible)
This keeps the Partner focused on decisions that require attention, not noise.
Reconciliation model: level-triggered pane lifecycle
tickmarkr closes exactly what it owns and no longer needs, no matter how any process died.
Tabs use short human labels (at most 20 characters); panes carry durable ownership names:
<taskId>— the task's tab, holding its worker and its judge/review/consult panescleanup · <taskId>— teardown generation tab for overflow attemptstickmarkr:watch:run:0:<runId>— daemon-owned Run boardtickmarkr:<role>:<taskId>:<attempt>:<runId>— durable judge, review, consult and worker pane names; display titles may be shorter
Reconciliation stays within the run's workspace. It can retire another run's panes only when this repository's journal evidence proves that run ended; live or unknown runs and other workspaces remain protected. Pane names outside the ownership contract are foreign. Board replacement additionally checks repository/run ownership and its acknowledged watch presence; matching a short tab label is never sufficient.
Desired-state reconciliation: A pure function computes the exact set of panes that should exist from the local journal at any moment:
- Worker panes for all in-flight task attempts
- Gate panes for unread judge/review/consult verdicts
- The watch pane (if running)
- Empty set (after engagement end)
The daemon reconciles at every safe point:
- Run start — reconcile this run and older runs proven ended in this repository
- Resume — reconcile the restarted journal state and close panes for superseded attempts
- After terminal events (task done, failed, human gate) — close the corresponding worker/gate pane and its emptied tab
- At engagement end — close remaining owned panes and tabs, with graceful board shutdown
visibility.keepPanes: forever disables this sweep. Reconciliation failures (herdr
unavailable, a pane vanished mid-sweep) do not replace gate verdicts; unconfirmed board
cleanup is journaled and reported.
Worktree lifecycle
Queue row 240 (D-2138): at run end, every journaled task checkout is removed once its
recovery ref is recorded, including parked and failed tasks. Clean merged checkouts
are removed silently. Dirty work is preserved under refs/tickmarkr/preserved/;
unmerged clean heads are pinned there too. The journal records the recovery ref and
removed checkout, and resume rebuilds from that work. Task branches and refs remain.
A crash or SIGTERM leaves checkouts until the next daemon start in the same checkout.
After its lifecycle row and reconciliation, that start sweeps other runs in this
checkout's runs directory only when their newest lifecycle row records a proven dead
pid, regardless of how they ended. Recovery and removal rows go into the owning run's
journal and name the sweeping run; outstanding checks and measured wall windows stay unchanged.
Only journaled task checkouts are touched: there is no worktree directory listing or
in-use check. Live or unrecorded pids, this run's checkouts, verification checkouts,
hand-made directories and sibling linked checkouts remain untouched; a refused start
sweeps nothing. visibility.keepPanes: forever disables both removal boundaries.
Staged bytes missing from the recovery tree, preservation failures and deletion failures keep the checkout and journal a run-level removal failure in the current run. Cleanup never changes the run's outcome or adds another run end.
Workspace trust
tickmarkr creates fresh git worktrees for change isolation. That is separate from each CLI's own workspace-trust behavior:
tickmarkr doctorinvokes each installed adapter's trust hook where supported and seeds trust where possible- Some CLIs show a "Workspace Trust" dialog; tickmarkr auto-answers only a recognized fingerprint once per slot
- Other blocked dialogs page you for manual approval
Spec formats
tickmarkr compiles several formats into its internal task graph. The most common:
| Format | Example |
|---|---|
| tickmarkr native (default) | tickmarkr init template with native spec marker |
| Markdown PRD | Any .md file with task sections and acceptance[] criteria |
| Spec Kit | A Spec Kit spec directory |
| GSD | A GSD .planning/ phase plan |
Compile fails loudly if it cannot recognize the format or if acceptance[] is missing. Use
--type native|prd|speckit|gsd to force a specific format.
Acceptance criteria (assertions)
Every task requires explicit acceptance[] criteria; compile fails without them. Three oracle types:
command <cmd>— pass if exit code 0; most reliabletest <name>— run a named test from your suite (must exist)judge <rubric>— LLM verdict against your rubric; fail-closed, never overrides failed command/test
At runtime, the scope gate derives git diff --name-only from the task base and compares it with
the spec-declared files. Out-of-scope edits fail unless the operator config explicitly allowlists
them; worker-declared deviations are recorded as notes, not authority.
Claude Code and Claude Desktop
The optional tickmarkr plugin adds the /tickmarkr-board pane and a session-start waiter:
- Claude Code:
claude plugin install tickmarkr --marketplace alzahrani-khalid/tickmarkr. - Claude Desktop: Plugins → Add marketplace (
alzahrani-khalid/tickmarkr) → Add plugin (tickmarkr).
Enabling the plugin runs tickmarkr wait --timeout 23h as a background task at every session start
and resume and loads the board module (hooks/board/register.tsx); both execute with the user's
permissions. The waiter wakes the session when the run ends, parks, a task fails or the daemon dies.
The board shows the current run's status and decisions.
The plugin and tickmarkr init --agent use the same skills:
- /tickmarkr-loop — compile a spec, review the routing plan, run the engagement, and commit the Markdown record
- /tickmarkr-auto — autonomous multi-phase runs (GSD milestones, etc.)
These are optional — the CLI works standalone. Skills are repo-scoped and ship in the npm tarball for agents working in projects that have run tickmarkr init --agent.
skills/ stays the single source for the plugin and installed skills; this repository's .claude/skills/ links
resolve there. The overseer skill links to the same
loop walkthrough for cockpit and decision guidance; skill names remain unchanged.
tickmarkr acp
tickmarkr acp is an ACP v1 agent that an editor launches over stdio. It runs no model
and accepts only console commands:
| Command | Behavior |
|---|---|
| /attach [run] | Watch the named run, or the current/default run. |
| /status [run] | Read the named run's current status, or the current/default run. |
| /compile <spec> | Run tickmarkr version --check first; compile only if it succeeds. |
| /plan | Run tickmarkr version --check first; plan only if it succeeds. |
| /run | Launch tickmarkr run --driver subprocess as a detached daemon, then watch it. |
| /resume <run id> | Launch tickmarkr resume <runId> --driver subprocess as a detached daemon, then watch it. |
Journalled runs appear as sessions. Both launch commands also check the version before launching; they accept no driver override or other flags.
A park shows its exact tickmarkr approve commands, who enacts each decision, and the
approval window's close time when a window is open. The live daemon enacts a release at
its next task boundary; without a live owner, run the printed tickmarkr resume command
after approving. If another run holds the repository lock, wait for it to end before
resuming. Window expiry is a lapse: approvals are swept again before final close, and
the daemon owns the run until it exits. A park with no permitted decision shows its reason.
ACP never decides: no ACP answer or client setting releases a park. Run a printed approval
command explicitly in a terminal.
Closing the editor or a session stops only the watch; nothing is signalled to the daemon or workers. ACP never stops a run.
Dock-launched editors may lack your shell's PATH. Use absolute paths to both Node and
the tickmarkr script. Replace both example paths below with your installed paths: find
Node with node -p 'process.execPath', then run npm root -g and append
/tickmarkr/dist/cli/index.js to find the global script (or use your checkout's dist/cli/index.js).
For Zed, merge this entry into settings.json
under agent_servers:
{
"agent_servers": {
"tickmarkr": {
"type": "custom",
"command": "/opt/homebrew/bin/node",
"args": ["/opt/homebrew/lib/node_modules/tickmarkr/dist/cli/index.js", "acp"],
"env": {}
}
}
}For JetBrains, add this entry to
~/.jetbrains/acp.json:
{
"agent_servers": {
"tickmarkr": {
"command": "/opt/homebrew/bin/node",
"args": ["/opt/homebrew/lib/node_modules/tickmarkr/dist/cli/index.js", "acp"],
"env": {}
}
}
}Other ACP clients use the same command and arguments. tickmarkr is not in the ACP registry; configure it as a custom agent.
Contributing
See CONTRIBUTING.md for development setup, the green bar (build/test/lint), and design invariants. Pull requests welcome; non-trivial changes should include test coverage.
Boundaries: this repo is a squashed export of private development — each release is a verified
snapshot, not a live mirror of every commit. Before tickmarkr 2.0, minor versions may break with every
break noted in CHANGELOG.md. Support is best effort for the latest version only;
accepted contributions are credited via Co-authored-by: on the release commit. Details in
CONTRIBUTING.md.
Documentation
Experimental execution policy — bounded recovery, cumulative task time, and repair selection.
FLEET.md — routing modes, steering syntax, tier provenance, and run flags (advanced reference)
LICENSE — MIT license
CONTRIBUTING.md — development setup and contribution guidelines
SECURITY.md — security policy and private vulnerability reporting
CODE_OF_CONDUCT.md — Contributor Covenant
