github Dicklesworthstone/ntm v1.36.0

3 hours ago

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_snapshots applies on first run. It only adds a table (the durable work snapshots behind ntm work-snapshot); existing rows are untouched (a1fb5774).
  • models.default_claude now defaults to empty (GH #334): a bare --cc pane launches with no --model and Claude Code uses its own default or saved model. The opus / sonnet / haiku / architect / fast aliases 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_claude explicitly (04ba2c7a).
  • ntm workflow run --resume continues the saved stage instead of clearing it and starting over. Use the new --restart to repeat an unfinished run from the beginning (20dd37bf).
  • blocked-on-ivan is no longer a built-in operator-gated label. Projects that used it must add it to the new [assign] operator_gated_labels key (29d1e7ea).
  • ntm serve / ntm web now bound async jobs to 4 concurrent workers and 64 queued jobs by default (--job-concurrency, --job-queue-capacity). Past that, POST /api/v1/jobs answers 503 with Retry-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, REST omp_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 is omp --auto-approve (plus --model, --thinking <effort>, --append-system-prompt when requested); [agents] omp, [models] default_omp / [models.omp], [prompts] omp_default, and [spawn_pacing.agent_caps] omp_max_concurrent configure it. omp_max_concurrent defaults to 8, omp's share of the host-wide robot spawn budget, sized so a full --spawn-omp=8 swarm is admitted (d66ff2df). ntm palette targets omp on key 7 (03f61b2b); --assign scores omp with the midpoint of the Claude and Codex capability profiles (0b551e0b); ntm quota, ntm rotate all-limited and --robot-quota-check report 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 · N block, or a listed Subagents panel; 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", coordinator agent_error), never idle-after-completion, including when omp draws F5 to Retry between the block and the composer; the recommendation names F5 (omp's retry key) and a continue prompt. omp panes are identified only by the exact omp executable basename, so libomp or rg omp do not match. No rate-limit state is inferred from transcript text (no omp rate-limit screen has been captured yet). --robot-dialogs reports 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 status and the coordinator's context-rotation trigger read omp's composer-border gauge (7% … 262K, including the compressed 69%󰁨262K form), so readings stay per pane when many omp panes share a directory; transcripts under ~/.omp/agent/sessions provide transcript usage too, and /compact and /clear are used for compaction. ntm sessions save binds each omp pane to its session through the transcript omp holds open (sub-agent transcripts excluded; profile, XDG and PI_CODING_AGENT_* stores honoured) and resume relaunches with omp --auto-approve --resume <id>.

  • Background pipelines run in detached worker processes (fb73d986, 98d7d635, d1ca923a, ffff34de): CLI --background runs, REST background runs and the new ntm pipeline resume --background re-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; --config and --ssh carry 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 reports cancellation_requested only 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_file inputs 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 parallel blocks (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_exec and pipeline_resume job 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 optional params.operation_id makes 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_spawn jobs accept dry-run, working directory, model/effort, readiness timeout, assignment and reservation options, plus launch_interval and startup_timeout; unknown or unsafe parameters are rejected instead of silently dropped. A top-level job session is 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/job target_session recover 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 sessions restore, 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. --watch streams observations, --wait-ready N waits for N verified candidates (optionally --require-reservations), and --refresh forces 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 assign planner 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 and file:line suffixes before reserving.

  • Assignment reservations are maintained and recovered (04a173b6, 3c2efc84, eeec6259, 4ba38e38, 2936dfd1): the coordinator and assign --watch renew 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=false explicitly, 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.go and src/service/*.go now conflict before src/service/main.go exists. 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 --files reads project files, directories and ** globs directly (no interactive s2p needed), within a measured rendered-token budget that keeps XML/Markdown structure closed and redistributes unused budget from small files. ntm context inject --build prepares 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 --retry repeats 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 status and ntm workflow recover (8fbb3a7d, 20dd37bf, 04448b81): inspect a workflow checkpoint (stage, per-pane receipts, a checkpoint_token) without a live tmux server, and record an independently verified delivered / not-sent outcome for one uncertain send so workflow run --resume can 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-dialog handle 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 include input_mode and displayed hotkeys.

  • ntm swarm --json launches 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-accounts now 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 dead status on the Agents page, and a taller pane viewer.

Changed

  • --robot-status and 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-timeout is marked deprecated.
  • The examples/agents/omp.toml plugin 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_GATE before 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-render with --enter=false reported 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-id the 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 send silently failed to deliver long prompts to OpenCode panes while reporting success (GH #333, 8114b9b5): prompts were typed with send-keys -l, and OpenCode drops typed bursts (multi-KB prompts entirely, sometimes a few hundred bytes), while ntm printed Sent 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_claude is 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 set models.default_claude for every bare --cc pane.
  • 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.
  • --panes selectors behave the same on every robot verb (#329, 32b64590, b470fc2f): --robot-probe, --robot-watch-bead, --robot-monitor and --robot-errors rejected W.P and %N selectors or matched bare N in every window, and --robot-restart-pane / ntm respawn --panes silently ignored a malformed selector and restarted only the rest. All now use the shared grammar and fail with INVALID_FLAG or PANE_NOT_FOUND before touching any pane.
  • The web dashboard showed no sessions or agents while agents were running (bf1b5872, 3b731728, 84b0a4fe): /api/v1/sessions read 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 the id/name/type fields the Agents page reads, and bead endpoints accept child IDs such as bd-1aae9.1.
  • Respawn dropped a pane's model pin (67a33ee0): ntm respawn, --robot-restart-pane and health --auto-restart-stuck relaunched with an empty template, so a pane pinned to a model came back on the default with no warning.
  • ntm doctor --json exited 0 on an unhealthy ecosystem (e032074c); it now exits non-zero while still printing exactly one JSON document.
  • ntm config get called live keys unknown (6982ca94, 7af5ea75): agents.claude_isolate_credentials, agents.claude_token_file, agent_mail.pane_badges and 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-boot and smart stagger (cb4897b2, f6a9f9df, 4b30a7ae, d66ff2df): diagnose, ensemble stop and the REST pane interrupt typed the literal text C-c into panes instead of sending Ctrl+C; --robot-markdown session tables counted OpenCode panes as "other"; ntm spawn --verify-boot timed 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-monitor defaults) dropped the lowest-index pane of every multi-pane window even when ntm had recorded it as an agent; and --stagger-mode=smart staggered 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_wedge requires both git and bead evidence; a malformed or failed br read is unavailable evidence rather than zero work. Productivity observations share one deadline, cap subprocess output at 8 MiB and collect evidence concurrently.
  • --robot-probe could 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/python relaunches the recorded agent instead of a REPL, and a slow-starting wrapper is no longer killed repeatedly with respawn-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 br queries 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 with background=true stays attached to its execution until it finishes.
  • Webhook delivery (dd9e4658, 3f08c83e): redirects and non-2xx responses are failures, Retry-After is 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 history pruning, 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-context continued 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 serve reports 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 | bash

Existing 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

Don't miss a new ntm release

NewReleases is sending notifications on new releases.