The Daemon
trkr start spawns a detached background process (trkr __daemon) with stdout/stderr appended to .trkr/daemon.log. A pidfile guards against double-starts; a stale pidfile from a crash is cleared automatically. trkr stop sends SIGTERM and waits up to 10s — the daemon finishes any in-flight stage before exiting.
The poll loop
Every 30 seconds the daemon reads all board items and, in order:
- PR watch — open tickets sitting in their workflow's terminal column are checked against their linked PRs. Every PR closed unmerged → labeled
trkr:blocked. In a custom workflow whose terminal column isn't Done, a merged PR also moves the ticket to Done (presets already terminate in Done, so there's nothing to move). - Sweep — board items with no Status are swept into their workflow's first column (Backlog on the presets).
- Dispatch — if no stage is currently running (one at a time), the daemon finds the first ticket that is: in some stage's waiting column (or, for
manualstages, moved by a human into the working column), nottrkr:blocked, and has zero openblocked_bydependencies (the frontier check). It spawns atrkr __stage <workflow> <stage> <itemId>child process for it.
Tickets naming an unknown or disabled workflow are logged and skipped. Each poll writes .trkr/daemon.json (pid, start time, last poll, item count, and the active dispatch: stage, ticket, workflow, start time) — this is what trkr status and trkr watch read.
Stage execution
Each stage runs as its own process:
- Move the ticket
from → during. - Ensure the ticket's worktree at
.trkr/worktrees/<issue>— created detached at HEAD on first use, reused across the ticket's stages and turns. The session checks out or creates the ticket branch inside it; the main tree is never touched. - Render the stage prompt and run the resolved provider's command in the worktree, with trace context in the environment.
- Evaluate the stage's gate. Pass → move to
to. Retry (e.g. CI pending) → leave in place, poll again. Refusal → a turn is consumed.
Sessions are stateless: the issue thread and committed artifacts are the only memory between stages.
Turns, kickouts, and blocking
The turn counter is a trkr:turn-N label on the issue, so it's visible on GitHub. On a gate refusal:
- The findings are appended to
.trkr/turns/<issue>.jsonand posted as an issue comment (**trkr: turn N/3** — <stage> verification failed), and the ticket is moved back to its workflow's first column to re-walk the pipeline from the start. - After
max_turns(default 3), the ticket is labeledtrkr:blockedand gets an escalation comment with the full findings history from every turn.
Every comment trkr posts carries a trkr trace <id> footer linking it to the telemetry trace that explains it. trkr unblock <issue> clears the turn label and history, removes trkr:blocked, and restarts the workflow.
Worktree lifecycle
One worktree per ticket at .trkr/worktrees/<issue>, resolved against the main repo root (the daemon's cwd never matters). It's removed when the ticket clears the terminal stage. Logs, prompts, and telemetry stay anchored to the main repo root under .trkr/.