Oh My Pi (omp) becomes a built-in agent type; pipelines, REST jobs and checkpoint restores become durable across process exits and crashes; work assignment verifies candidates against the tracker, Agent Mail reservations and mutex groups before dispatch. Also fixes the three issues filed against v1.35.x (GitHub issues #332, #333, #334).
Upgrade notes
- Schema migration
024_runtime_work_snapshotsapplies on first run. It only adds a table (the durable work snapshots behindntm work-snapshot); existing rows are untouched (a1fb5774). models.default_claudenow defaults to empty (GH #334): a bare--ccpane launches with no--modeland Claude Code uses its own default or saved model. Theopus/sonnet/haiku/architect/fastaliases now pass Claude Code's family aliases instead of dated model IDs. If you relied on ntm pinning a specific Claude model, set[models] default_claudeexplicitly (04ba2c7a).ntm workflow run --resumecontinues the saved stage instead of clearing it and starting over. Use the new--restartto repeat an unfinished run from the beginning (20dd37bf).blocked-on-ivanis no longer a built-in operator-gated label. Projects that used it must add it to the new[assign] operator_gated_labelskey (29d1e7ea).ntm serve/ntm webnow bound async jobs to 4 concurrent workers and 64 queued jobs by default (--job-concurrency,--job-queue-capacity). Past that,POST /api/v1/jobsanswers 503 withRetry-After(f3e3401b).
Added
-
Oh My Pi (
omp) is a first-class agent type (aa0182bc, 610bf2c9, cb4897b2, e48b1bc9, fec3f003, 327036fa):ntm spawn/add/create/adopt --omp=N[:model[:effort]],--robot-spawn --spawn-omp, RESTomp_count, respawn/restart/resume, personas, recipes, session profiles, the spawn wizard,ntm send --omp, activity/assign/rebalance/review-queue filters, dashboards, analytics, events, the VS Code picker and the web UI all accept omp. The default launch isomp --auto-approve(plus--model,--thinking <effort>,--append-system-promptwhen requested);[agents] omp,[models] default_omp/[models.omp],[prompts] omp_default, and[spawn_pacing.agent_caps] omp_max_concurrentconfigure it.omp_max_concurrentdefaults to 8, omp's share of the host-wide robot spawn budget, sized so a full--spawn-omp=8swarm is admitted (d66ff2df).ntm palettetargets omp on key7(03f61b2b);--assignscores omp with the midpoint of the Claude and Codex capability profiles (0b551e0b);ntm quota,ntm rotate all-limitedand--robot-quota-checkreport omp as having no quota API instead of skipping it (0ca86156); the dashboard cost panel prices omp panes by their configured model (6c90e45f). -
omp pane state from its live TUI (36000c67, 58b0a719, 99e46df4, 036b6bdf, b65b7004): captured against omp v18.2.3 in the nerd, unicode and ascii presets. Working = spinner and timer in the composer's top border, the Esc-hint activity line, a pending
Steering · Nblock, or a listedSubagentspanel; idle = a quiet composer with nothing below it. A failed turn's rule-framed provider-error block (server_error: ERROR,401 Invalid API Key,Dismissed when you send your next message.) is a retryable error (ERROR_STATE,indicator_basis: "provider_error", coordinatoragent_error), never idle-after-completion, including when omp drawsF5 to Retrybetween the block and the composer; the recommendation namesF5(omp's retry key) and a continue prompt. omp panes are identified only by the exactompexecutable basename, solibomporrg ompdo not match. No rate-limit state is inferred from transcript text (no omp rate-limit screen has been captured yet).--robot-dialogsreports a staged large paste as paste limbo and a selector overlay as a dialog. -
omp delivery and control (5e3dfe17, fec3f003): Enter submits once; a message sent to a busy omp pane is queued as steering and confirmed when the payload leaves the composer, with one rescue Enter if it is stranded. A blank pane during omp's first second, an open completion list, or a selector overlay is refused rather than typed into. Interrupt uses Escape for omp (Ctrl+C only clears omp's draft and a double press quits) across
ntm interrupt,--robot-interrupt, diagnose, ensemble stop and the REST pane interrupt; graceful exit is Escape, Ctrl+C, Ctrl+D. -
omp context accounting and session resume (fec3f003, aaeb9e63, 92282cb9):
--robot-context(source: "status_bar"),--robot-snapshot,ntm statusand the coordinator's context-rotation trigger read omp's composer-border gauge (7%…262K, including the compressed69%262Kform), so readings stay per pane when many omp panes share a directory; transcripts under~/.omp/agent/sessionsprovide transcript usage too, and/compactand/clearare used for compaction.ntm sessions savebinds each omp pane to its session through the transcript omp holds open (sub-agent transcripts excluded; profile, XDG andPI_CODING_AGENT_*stores honoured) and resume relaunches withomp --auto-approve --resume <id>. -
Background pipelines run in detached worker processes (fb73d986, 98d7d635, d1ca923a, ffff34de): CLI
--backgroundruns, REST background runs and the newntm pipeline resume --backgroundre-exec ntm as a worker that survives the launching command or server exiting, instead of a goroutine that died with it. Worker progress is journaled and relayed over the existing WebSocket events;--configand--sshcarry over to the worker; dry runs stay synchronous and write nothing. -
Pipeline runs have cross-process ownership and cancellation (e6edbc2b, bd954462, 02d7356f): every run (foreground, background, REST, Jobs, resume) holds an OS-level per-project run lock through cleanup, so two resumes of one run cannot both execute; the loser gets
PIPELINE_RUNNING(HTTP 409) without touching the checkpoint. A fresh process can cancel a run it does not own; it reportscancellation_requestedonly after the owner acknowledges, and requests are token-fenced so an old request cannot cancel a replacement run. REST cancel/list/inspect read the configured project's durable state instead of a process-global registry keyed only by run ID, which could have cancelled a same-named run in another project. -
Pipeline recovery uses frozen inputs (2c2c52d3, 2be31295, 34c43c23, 4bc393eb, f358b7d8): before dispatch, a run's workflow definition, its existing template files and its
prompt_fileinputs are copied into a private, content-addressed snapshot, and the run executes that copy. Editing the workflow or its templates mid-run no longer changes what a resume executes; a missing or corrupted snapshot fails the resume instead of falling back to the mutable original. Templates generated during execution remain live inputs and are warned about. -
Pipeline resume does not resend confirmed agent prompts (4ee45168, a7df9acb, 718222c0, 7e5064ee, 12230ccc): prompt and template dispatches are journaled before sending; resume keeps observing an already-delivered request instead of pasting it again, and an uncertain delivery requires an explicit restart. This covers loop, foreach, foreach_pane and nested parallel/branch bodies. A resumed foreach whose item list changed after partial dispatch is refused, and forced loop rewinds clear stale downstream outputs instead of reusing them.
-
Mixed and nested
parallelblocks (2b3d8fb9, 2ac3c158): parallel children can now be commands, templates and composite steps, not only prompts, and inherit retries, cancellation, checkpoint gates and hooks. Branch bodies follow the same lifecycle (conditions, retries, hooks, checkpointed outputs), and an unresolved branch selector is rejected before any shell command runs. -
REST jobs for pipelines, swarms and restores are durable (e3ebd1fd, 0b4ec465, 0c2dfe64, 095e743f, 8cb79e1b, f94c5382): new
pipeline_execandpipeline_resumejob types. Jobs are journaled before dispatch and after cleanup, so completed, failed and cancelled results survive a server restart; a job interrupted by a crash is reported as outcome-unknown and never replayed. An optionalparams.operation_idmakes a retried request replay the original result instead of running again. Swarm-spawn jobs checkpoint live startup (pane IDs, launches, readiness), which job GET/list expose while work is running. -
Job scheduling and swarm launch controls (f3e3401b, 536d0d3a, fcec2e03, 2e678888, 5a78887c, 9d21fff8, d387ecb0): jobs are admitted through a bounded FIFO queue and serialized per tmux session (and per saved run for resumes) while independent sessions run in parallel.
swarm_spawnjobs accept dry-run, working directory, model/effort, readiness timeout, assignment and reservation options, pluslaunch_intervalandstartup_timeout; unknown or unsafe parameters are rejected instead of silently dropped. A top-level jobsessionis now honoured and a conflicting nested session is rejected. -
Checkpoint restore into a separate session, cancellable and journaled (852fd63f, efd82a74, 5df64389, 59a2365a, 5a1c25a3):
ntm checkpoint restore --as <session>and REST/jobtarget_sessionrecover alongside a live session without modifying it or the stored checkpoint. Restores can be cancelled at every phase and report the stage reached on partial failure. Saved context is sent only after the restored agent shows two fresh idle readings, and never into a user shell. -
Exact launch settings survive recovery (64070ddf, 6a9a976e, 4a65220e): spawn and add record each pane's rendered command, model, persona, reasoning effort, account profile, credential isolation and worktree. Checkpoint restore,
ntm sessionsrestore, native conversation resume and every resilience restart path replay those settings instead of reconstructing them from pane titles, and preflight every saved launch before replacing a session. -
ntm work-snapshot: source-verified ready work (a1fb5774, dd0829ac, c1d2375a, 66f598db): a read-only query that reports ready candidates checked against the tracker JSONL, git HEAD, eligibility policy, mutex groups and live Agent Mail reservations, persisted per project.--watchstreams observations,--wait-ready Nwaits for N verified candidates (optionally--require-reservations), and--refreshforces a new collection. Source drift or a failed read is reported as unavailable, never as an empty queue. -
Work eligibility and mutex groups are checked before dispatch (3ef99a18, 5c3aae2e, 25bb5f10, 7ffd8934, b9a108f8, 7fa4a9d9, 8ae5a96d, 403dbdd7, 575224a2, 2154af0b, 38ea93a7): the bv triage cache is bound to the tracker JSONL digest and HEAD rather than a TTL alone. Recommendations filter lifecycle, ownership, dependency, deferral and mutex exclusions before applying the caller's limit, so a blocked top-N no longer hides eligible work below it; mutually exclusive tasks are never recommended together, and the atomic tracker claim enforces mutex groups across competing processes. Live peer reservations exclude reserved candidates. A gated-only or all-ineligible queue is reported as nothing to do rather than as an error.
-
Better assignment batches (8224b5df, 74a20ae9, 6cae70fb, aeaef0cc): the balanced
ntm assignplanner uses min-cost maximum flow and the coordinator uses maximum-weight matching instead of greedy selection, so specialist-only work is no longer stranded by a flexible task taking its only worker. Session capacity is consumed across the whole batch, and same-named workers in different sessions are kept distinct. File intent extracted from task text ignores URLs and issue references and normalizes Markdown links andfile:linesuffixes before reserving. -
Assignment reservations are maintained and recovered (04a173b6, 3c2efc84, eeec6259, 4ba38e38, 2936dfd1): the coordinator and
assign --watchrenew the exact reservation leases of delivered assignments in their final 15 minutes, even when automatic assignment is disabled, and stop admitting new work if protection cannot be confirmed. When a pane running delivered work is proven gone, its reservations are released by ID and the claim is reopened before new work is assigned. -
Agent Mail grants are verified before they are trusted (7dee1382, 09df4491, 57fd740c, 38f4b251, bd2c8b71, 6ffa27f2) (refs #328): reservation readback follows every page instead of stopping after 20 leases, and every grant is checked against an independent, project-scoped read of active reservations (owner, path, exclusivity, expiry). Shared reservations send
exclusive=falseexplicitly, and a reply that does not cover every requested path exactly once is treated as unverified. Verification reads only the reservation resource, which current Agent Mail servers provide, and is time-bounded. -
Reservation overlap compares glob languages (7440dbd2, ab736233, 0bd96535):
src/*/main.goandsrc/service/*.gonow conflict beforesrc/service/main.goexists. One bounded matcher is shared by the coordinator, the Agent Mail staged-file guard and the lock-risk advisor; malformed or over-complex patterns are treated as possibly overlapping, and a lease with no expiry counts as active. -
Handoff reservation transfer is fenced by lease ID (4705ace7, 1e75701f, b3a8407d, 712c9832, d02d0d23, 4082835e, 189776eb): handoffs capture each reservation's ID and owner; release, renewal and cleanup act on those exact IDs, so a replacement lease on the same path can no longer be released through its old path. Incomplete or unverified grant replies stop cleanup and retries, renewals are read back before being reported, and rollback receipts are kept for recovery.
-
Spawn delivers the latest project handoff (1c37fc73): an ordinary spawn loads the newest readable local handoff for the project and delivers its task, decisions and next steps before the user prompt. JSON recovery status names the source; recovery opt-outs are honoured and remote or mismatched handoffs are rejected.
-
Native context packs and
ntm context inject --build(90efe6c9, 626e81aa, 4c3b1886, bc9b1bec, 4d77b49c, ab9a691b, af9a8041):ntm context build --filesreads project files, directories and**globs directly (no interactives2pneeded), within a measured rendered-token budget that keeps XML/Markdown structure closed and redistributes unused budget from small files.ntm context inject --buildprepares one pack per agent type and delivers it through the agent-aware sender (--task,--bead,--files,--pack <id>,--dry-run), reporting per-pane delivered/uncertain/not-attempted outcomes; a failed send is no longer reported as a successful injection. Packs with source files are always rebuilt, so dirty edits are never served from cache. -
Context rotation and compaction confirmations execute and persist (993cec1a, 61b0fa61, fc06fcd9): CLI and dashboard confirmations run the rotation engine and return its outcome, with receipts persisted across processes (
ntm rotate context confirm --retryrepeats an interrupted one). Rotation waits for a real summary from the original agent, launches the replacement with the original model, effort, persona, worktree and credentials, and keeps the predecessor if the handoff fails. -
ntm workflow statusandntm workflow recover(8fbb3a7d, 20dd37bf, 04448b81): inspect a workflow checkpoint (stage, per-pane receipts, acheckpoint_token) without a live tmux server, and record an independently verifieddelivered/not-sentoutcome for one uncertain send soworkflow run --resumecan continue. Each stage's delivery plan is journaled before sending. Review approvals are bound to the stage and pane process, so stale scrollback or a prompt echo cannot advance a workflow. -
--robot-sequence advance --sequence-expected-position(494525ac, 08067007, 6987df6f): a retried or stale advance returns the current prompt instead of skipping one. Sequence state is guarded by per-sequence OS locks, so concurrent processes no longer lose advances. -
--robot-dialogs/--robot-answer-dialoghandle cursor and hotkey menus (82a13bc3) (refs #325): Claude and Antigravity cursor menus and Grok's immediate y/n workspace gate are inspected and answered, with the selection re-captured and verified before Enter. Reports includeinput_modeand displayed hotkeys. -
ntm swarm --jsonlaunches and reports real outcomes (70a6222a, 7b7abc03): JSON mode executes the swarm and returns ordered creation, launch, prompt, readiness and cancellation receipts, with a failure status for incomplete runs; plan and dry-run modes stay read-only.--auto-rotate-accountsnow starts resident account monitors for eligible local Linux Claude and Codex panes and reports their eligibility and health. -
Operator-gated labels are configurable (29d1e7ea):
[assign] operator_gated_labels, globally or in a project.ntm/config.toml, adds labels to the built-in gate set. Configuration can only widen the gate, so a repository config cannot un-gate work. -
Web dashboard (522fccd5): a bead detail dialog, Open / In Progress / Blocked / Ready totals that filter the board, pane titles and a
deadstatus on the Agents page, and a taller pane viewer.
Changed
--robot-statusand other robot flag help name the flags that exist (97dbfc2d): examples taught retired spellings (--triage-limit,--spawn-timeout,--files-window,--inspect-index,--beads-status); they now use--limit,--timeout,--window,--index,--status, and--ready-timeoutis marked deprecated.- The
examples/agents/omp.tomlplugin preset is superseded by the built-in type; built-in agent types take precedence over a plugin of the same name (99e46df4). [spawn_pacing.agent_caps]are documented as what they are (d66ff2df): summed into one host-wide agent budget for robot spawn admission, not per-type launch concurrency.- Normal dispatch refuses live workspace-trust prompts (d6d088e9) (refs #325): a Claude, Antigravity or Grok trust menu now returns
PANE_INTERACTIVE_GATEbefore any keys are sent, so the prompt is not typed into the menu (or into Grok's immediate quit hotkey) and the assignment can be retried after--robot-answer-dialog. ntm never approves trust automatically.
Fixed
--robot-send --verify-renderwith--enter=falsereported a landed paste as failed on idle Claude panes (GH #332, 1139b7b0): the check compared the baseline with one capture taken the instant the keys were written, before Claude Code redrew its composer, and with--op-idthe false failure was recorded and replayed. A delivered pane whose screen is unchanged is now re-captured every 100ms for up to about 2s, stopping as soon as the render changes.ntm sendsilently failed to deliver long prompts to OpenCode panes while reporting success (GH #333, 8114b9b5): prompts were typed withsend-keys -l, and OpenCode drops typed bursts (multi-KB prompts entirely, sometimes a few hundred bytes), while ntm printedSent to 1 pane(s)and exited 0. OpenCode prompts now always go through bracketed paste; a composer that is still empty before Enter fails the send, and a new submission check confirms the prompt left the composer, with one rescue Enter.- The default Claude model was a hard-coded ID that went stale with each model release (GH #334, 04ba2c7a, e7ae5818): it also overrode the model saved in Claude Code.
models.default_claudeis now empty (see Upgrade notes). Status, robot status and the dashboard still estimate context and cost for such panes against the Claude Opus family, and spawn no longer tells users to setmodels.default_claudefor every bare--ccpane. - The dashboard Cost panel panicked at table widths 36-43 (#331, ac7e550e): rows carried five cells for four columns (
index out of range [4] with length 4) once three or more agents were listed. Rows are now built from the column list itself. Reported with a root-cause analysis by @ericziko. --panesselectors behave the same on every robot verb (#329, 32b64590, b470fc2f):--robot-probe,--robot-watch-bead,--robot-monitorand--robot-errorsrejectedW.Pand%Nselectors or matched bareNin every window, and--robot-restart-pane/ntm respawn --panessilently ignored a malformed selector and restarted only the rest. All now use the shared grammar and fail withINVALID_FLAGorPANE_NOT_FOUNDbefore touching any pane.- The web dashboard showed no sessions or agents while agents were running (bf1b5872, 3b731728, 84b0a4fe):
/api/v1/sessionsread only a store the spawn path never writes; it now merges live tmux sessions (bounded to 5s, answering 504/503 rather than hanging or 500 when tmux fails). The agents endpoint returns theid/name/typefields the Agents page reads, and bead endpoints accept child IDs such asbd-1aae9.1. - Respawn dropped a pane's model pin (67a33ee0):
ntm respawn,--robot-restart-paneandhealth --auto-restart-stuckrelaunched with an empty template, so a pane pinned to a model came back on the default with no warning. ntm doctor --jsonexited 0 on an unhealthy ecosystem (e032074c); it now exits non-zero while still printing exactly one JSON document.ntm config getcalled live keys unknown (6982ca94, 7af5ea75):agents.claude_isolate_credentials,agents.claude_token_file,agent_mail.pane_badgesand every key under[command_hooks],[retry]and[routing]answered "unknown config path". Every TOML key is now readable, and removed keys are named as such.- Pane interrupt, pane selection,
--verify-bootand smart stagger (cb4897b2, f6a9f9df, 4b30a7ae, d66ff2df): diagnose, ensemble stop and the REST pane interrupt typed the literal textC-cinto panes instead of sending Ctrl+C;--robot-markdownsession tables counted OpenCode panes as "other";ntm spawn --verify-boottimed out on healthy panes of every agent type that started working on a delivered spawn prompt (such panes now count as booted, while unprompted panes must still be idle);--robot-is-working(and--robot-agent-health,--robot-smart-restart,--robot-monitordefaults) dropped the lowest-index pane of every multi-pane window even when ntm had recorded it as an agent; and--stagger-mode=smartstaggered omp-only spawns on Anthropic's backoff instead of omp's own bucket. - Robot waits and stall diagnosis no longer certify what they did not observe (da56121a, 7e0132de, 44a9e758, e47d9dfa, dfa214de, 8317f7c1): an ALL wait no longer completes on a pane it could not read, and attention evidence is kept across polls and replay pages.
suspected_wedgerequires both git and bead evidence; a malformed or failedbrread is unavailable evidence rather than zero work. Productivity observations share one deadline, cap subprocess output at 8 MiB and collect evidence concurrently. --robot-probecould report an operational failure as a stuck agent (ad5beca9): probes are bound to the physical pane ID, recheck identity after slow captures, report capture or send errors as unknown rather than unresponsive, and never escalate on them.- Session and checkpoint restore could leave broken panes or loop (2b44cba9, b8508e5f): saved topology, directories and commands are validated before a session is replaced, and per-pane launch failures are reported. A pane captured as a bare
node/bun/pythonrelaunches the recorded agent instead of a REPL, and a slow-starting wrapper is no longer killed repeatedly withrespawn-pane -k. - Checkpoint export and import were not atomic (6ac8a230, 5df672f8, 091618ca): exports are staged and renamed into place (a failed export no longer destroys the previous file) with scrollback kept 0600. Import verifies the gzip trailer so a truncated or corrupt archive is rejected, and on Linux and macOS publishes a whole checkpoint directory in one atomic exchange; other systems stage create-only imports and refuse overwrite.
- A pipeline could keep dispatching after its checkpoint failed to save (82dda265, 276f7593, b56c0240): a checkpoint write failure now stops the run, even with
on_error: continue. Background run status keeps the fatal cause after the worker exits, and a later failure is not hidden behind an earlier warning. - Pipeline subprocesses and pane locks (6585b817, 6f769c82, e46330ef, bc2f2e5c, 35e5e379): on Linux, cancellation waits for and kills every member of a command's process group instead of treating shell exit as completion; branch predicates, foreach sources and
brqueries have bounded output. The per-pane dispatch lock now also holds across different project roots and when no project directory is set. An async REST pipeline job withbackground=truestays attached to its execution until it finishes. - Webhook delivery (dd9e4658, 3f08c83e): redirects and non-2xx responses are failures,
Retry-Afteris honoured and 408 is retried; retries run on the worker pool so one slow endpoint no longer blocks the rest, and the retry backlog is bounded, with dead letters kept on overflow and shutdown. - Event log history could be lost (c295f23d, 276e2fdb, 4e200503): a failed retention rotation no longer drops history; appends and rotation are coordinated across ntm processes so one process cannot write to a file another just replaced; and a partially written record no longer swallows the next event.
ntm historypruning, export and filtering (44263204, 2a3cf187, 99cf6174): pruning stops on an incomplete read or wrong key instead of rewriting a partial file; plaintext exports are written atomically at 0600 and cannot target the history file itself; session and search filters combine correctly, and full-ID lookups no longer return null or panic.ntm rotate --preserve-contextcontinued before re-login finished (9dbcf5b4, 4b00973f): old scrollback from a previous login could be read as success. The wait now tracks only the current attempt and requires completion.- Daemon supervision (06783b31, 5d013e67, 130043e8): failed relaunches are retried within the restart budget instead of leaving the daemon down; two ntm processes can no longer both launch the same project daemon; health probes are tied to the daemon generation they check and reject truncated or mismatched responses.
ntm memory servereports the real failure after exhausting retries. - Robot send memory could come from the wrong project (fc17a38f): memory retrieval and feedback are resolved from the target session's panes and project, not the caller's directory.
Install / upgrade
curl -fsSL https://raw.githubusercontent.com/Dicklesworthstone/ntm/main/install.sh | bashExisting installs: ntm upgrade. Homebrew: brew upgrade dicklesworthstone/tap/ntm. The VS Code extension is attached as ntm-vscode-1.36.0.vsix (code --install-extension ntm-vscode-1.36.0.vsix).
Each archive has a matching .sha256; SHA256SUMS covers all of them.
Full Changelog: v1.35.1...v1.36.0