Repository navigation
v2.20.0
[2.20.0] — 2026-09-29
Added
-
agent execreports subagents on every runtime, not only claude (#387, capabilityagent-exec-subagent-events, agent-exec protocol rev 24). On any other runtime, a tool call whosetool_activityhaskind:"task"is now followed by asubagentstartedevent, and its end byfinishedwithstatus(completed,failed,denied, orstoppedon cancel/timeout) and asummaryof up to 500 characters of its output.idandtool_use_idare the call'stool_activityid. The events are synthesized fromtool_activity, so they have noprogressphase and nousage. opencode (task), cline (spawn_agent,team_*) and dsh (subagent) mark such calls themselves; other runtimes gettaskfrom the shared tool-name table (Task,Agent,spawn_subagent,invoke_subagent,browser_subagent, …). claude keeps its SDK-based events and gets no duplicates. New golden fixturedoc/agent-exec-protocol/fixtures/subagent-synth.ndjson. -
agent test --jsontakes--sandboxand--env, and reportsnative_sandbox(#474, agent-exec protocol rev 25, capabilityagent-test-sandbox).--sandbox read-only|workspace-write|fulland repeatable--env KEY=Vmean what they mean foragent exec, and are checked the same way. The result has a newnative_sandboxfield with the sandbox the vendor CLI really ran with, taken from the turn'sstartevent (nullwhen the turn never started). A mode the runtime does not have, such as--sandbox workspace-writeon pi, returnsstatus: "error"witherror.code: "unsupported". The test turn keepsscopedaccess, and neither flag can loosen the sandbox: an org git level belowpushstill caps codex and grok atworkspace-write. Callers such as mono-agent can now test a runtime inside the sandbox their real turns use. -
agent exec --sandboxhas real restricted modes on more runtimes, and--sandbox-fallbackmakes an unsupported mode non-fatal (#482, agent-exec protocol rev 26, capabilitiesagent-exec-sandbox-restrictedandagent-exec-sandbox-fallback). 2.19.0 offeredread-only/workspace-writeonly on codex, grok and dsh, and any other mode was a fatalunsupported, so a caller sending one--sandboxvalue to every runtime broke claude and copilot chats. New moderestricted: the CLI's own approval rules apply and every call they would ask about is refused (a rule set, not a file-system boundary). New per runtime, each checked with a live turn that tried a write and a shell command: copilotread-only(--deny-tool=write --deny-tool=shell),workspace-write(--allow-tool=write --deny-tool=shell, edits confined to the cwd and temp dir, no shell) andrestricted; antigravityrestricted(no--dangerously-skip-permissions); opencoderestricted(edit/bash/task/external_directory ask, every ask rejected); pi and pi-rpcread-only; clauderead-onlyandworkspace-writewith--access scopedorread, enforced by monomind's own tool gate (native_sandbox: "monomind").--sandbox-fallback fail|strictest|run(also onagent test):failstays the default;strictestruns the closest mode at least as strict as the request (else the strictest there is);runruns the runtime default. A replaced mode emits astatusnotice, andstartnow carriessandbox_requestedandsandbox_applied.agent scan --jsonentries gainsandbox_mode_reports, and theagent test --jsonresult gainssandbox_applied. Without--sandbox, every runtime's argv is unchanged.
Changed
-
The dashboard no longer starts at every session start unless you opt in (#423). The SessionStart hook
control-start.cjsstarted the Control Room server (:4242) for every initialised project, and the servers stayed resident: one machine had 8, two at 0.7–0.9 GB RSS. The hook now does nothing, and prints nothing, unlessMONOMIND_DASHBOARD_AUTOSTART=1is set or.monomind/dashboard.jsonhas{"autostart": true}(written by the newmonomind init --dashboard);MONOMIND_DASHBOARD_AUTOSTART=0turns it off for a project that opted in. A server that is already running is left alone.monomind uistarts it by hand andmonomind org runstill starts it unlessMONOMIND_DASHBOARD_AUTOSTART=0. Existing projects: the hook ships with the package helpers, so updating turns auto-start off there too; runmonomind init --dashboardin a project to keep it. Without a running dashboard the per-prompt Second Brain lookup uses its keyword fallback. -
monomind route taskismonomind pick --agents, and the routing hooks carry pick's verdict (#430).route task(and baremonomind route "<task>") ranked through the picker but always named an agent: the top of a ranking pick called unconfident (shown as e.g. 72%), orcoderwhen nothing matched, whilepicksaid "no confident match". It now runs pick and prints exactly its output, text or--json;data.agentIdis set only when pick is confident.hooks_route,hooks_pre-taskandhooks_explainreturn pick'sconfidentflag (hooks_route also itssummary), pre-task names a primary agent only when pick is confident, and explain says when the top is below the bar. The hooks no longer load.claude/helpers/router.cjs, which no hook used for selection; the file stays for scripts and doctor.route semanticand the route layer (@monoes/routing) are marked deprecated in help and docs, not removed.
Removed
- Hooks and helpers that ran every session, turn or prompt and did nothing (#417).
monomind initno longer registers the SessionStartauto-memory-hook.mjs importand Stopauto-memory-hook.mjs synchooks (both only printed "skipped — AutoMemoryBridge removed"), the SubagentStarthook-handler.cjs statushook or the Notificationhook-handler.cjs notifyhook (both only printed[OK] Hook: …). With no hooks left, it writes noStoporNotificationevent, and the init wizard no longer offers them. The helpersauto-memory-hook.mjs,learning-service.mjs(nothing calledrecord()) andmemory-palace.cjs(nothing wrote its drawers, so its session-startwakeUpinjection was always empty) are no longer installed. The UserPromptSubmitroutehook no longer runs the[INTELLIGENCE]lookup, which rewrote.monomind/data/ranked-context.json(~870 KB) on every prompt and matched nothing;intelligence.cjsdropsgetContext()andbootstrapFromDb().init upgradeleaves the old hooks and files of an existing install in place (they stay harmless no-ops);init --forceremovesauto-memory-hook.mjsand its settings hooks.intelligence-outcomes.jsonlis still written: session-end success detection,doctor's route-outcome correlation andhooks intelligence trainread it.
Fixed
-
A role's git guard no longer leaks into repositories its own tests create (#481, follow-up to #298). Roles below
policy.git: 'push'gotcore.hooksPathandcore.excludesFileas process-wideGIT_CONFIG_*entries, so every git call in the process tree saw them, including a test's throwaway repo in$TMPDIR:git add .claude/settings.jsonthere failed as ignored by the guard's excludes file (cleanup-after-init-e2e.test.ts). The guard now writes both settings to agitconfigfile in its state dir and exports onlyincludeIf.gitdir:<common dir>andincludeIf.gitdir:<common dir>/worktrees/entries that include it, so they apply in the org's protected repositories and all their linked worktrees and nowhere else. Inside those repositories the guard is unchanged: pushes and, atread/none, ref updates are still refused. The transport and credential settings (protocol.file.allow=never, the resetcredential.helper, failing askpass/ssh commands) stay process-wide, so a scratch repository still cannot push. -
org events --follow,org logs --followandorg watchexit when nobody is reading (#433). An idle follower only ever stopped on SIGINT/SIGTERM, so one orphaned by its parent (or whose reader had closed the pipe) polled a finished run'sbus.jsonltwice a second forever. The follow loop now stops when the process is re-parented (its parent pid changes), when stdout errors (EPIPE) or closes, and on SIGHUP. It also exits 0 once the followed run has closed:runtime.jsonrecords the run as stopped or crashed, or the run'sorg-stoppedevent was seen andruntime.jsonbelongs to a newer run; any remaining events are drained first. A live run is followed as before. Each tick now reads only the bytes appended since the last one instead of the whole file. -
The README no longer says installing runs no download scripts (#428). The Trust & Security table said "No post-install scripts that download code" and called tree-sitter a native addon. A fresh
npm install monomind@2.18.5adds 362 packages and 1.3 GB ofnode_modules, and three install scripts download binaries: puppeteer (657 MB of Chrome into~/.cache/puppeteer), onnxruntime-node (CUDA libraries from NuGet on Linux x64) and better-sqlite3 (a prebuilt addon). The table now gives those numbers, anddoc/privacy.mdhas a new "Install-time downloads" section listing each install script, what it fetches, and thePUPPETEER_SKIP_DOWNLOAD=1 ONNXRUNTIME_NODE_INSTALL=skipsetting that brings the install down to 981 MB with no Chrome. -
The mastermind "Paperclip" admin skills say what they actually do (#422).
mastermind-approval-detailread.monomind/orgs/<org>-approvals.json, which does not exist (the queue is.monomind/orgs/<org>/approvals.json), so it stopped with "No approvals file" on every real org. It now lists, shows, approves and denies throughmonomind org approvals,org approveandorg deny, which reach a running org's daemon; the comment, revision and resubmit actions nothing backed are gone.mastermind-secrets,-environments,-plugin-manager,-invites,-invite-landing,-join-queueand-accesswrite side files no runtime code reads. Each now says "bookkeeping only — not enforced by the Org Runtime" in its description and at the top of its body, and the claims of effects that never happen (secrets "consumed by agents", environments controlling where agents run, a disabled plugin not loading) are removed. 43 skills said they were "invoked bymastermind:X" for slash commands that do not exist; those lines now name only the skill's own/mastermind-Xentry. -
cleanup --forcekeeps the helpers a git-tracked settings file still runs (#448). Cleanup never edits a git-tracked file, so when a team committed.claude/settings.jsonbut not.claude/helpers/, the #401 fix could not strip the hooks and cleanup deleted the helpers they run: every prompt and tool call then failed. Cleanup now keeps the whole helpers directory those hooks and the statusLine run (listed as "kept: referenced by tracked .claude/settings.json"), and both the preview and--forceprint the dangling commands with the manual edit that completes the uninstall: delete them from the settings file, commit, re-runcleanup --force. -
doctor's Routing Learning check no longer reports accuracy from too little or one-sided data (#425). It printed "routing accuracy 100%" from 13 outcomes that were all marked successful. It now reports the sample size and the PICK follow rate (how often the agent used matched the recommendation), shows "insufficient data" as info (not pass) below 30 outcomes, and prints no accuracy when every outcome carries the same flag (warn). The
routing-feedback.jsonlfallback follows the same rules. -
monomind initruns its doctor pass once, after all its writes, and prints only problems (#425). The embedded doctor ran before init's last writes and printed each check, then its post-fix re-check, so one run said "No .gitignore found" and then "All … gitignored" (and "1 stale" then "match").initnow runs it at the end, applies the same fixes silently, prints only the warnings and failures left after them, then a one-lineHealth check: N passed, W warning(s), F failedsummary. -
monoswarm,autopilotandagent spawnsay they start nothing (#418).monoswarm initrecorded"status": "running"with an empty roster andmonoswarm statusshowed a hard-coded 5% progress for a swarm with no tasks. The state file now records"initialized", status readsrecorded (no agents started)and shows progress only from recorded task files (n/awhen there are none), andmonoswarm startshows a "Suggested Agent Roster (not started)" instead of an "Agent Deployment Plan".agent spawnprints "recorded in the agent store" and that no process was started (pointing atmonomind agent exec); theagent_spawnMCP tool returnsstatus: "registered"with the same note.monoswarmandautopilotare marked deprecated in help,autopilot statusand the docs;autopilot statussays when no task source has tasks, since nothing writesswarm-tasks.json. Removing both commands is left for a later release. -
Grep/search
[MONOGRAPH_HINT]s no longer point at test mocks (#447). The pre-bash and pre-search hint lookups inhook-handler.cjstook the first matching graph row, which was often a test (grep bridgeStoreEntryhinted…/__tests__/hooks-core-commands.test.ts:17), and since #409 those hints reach the model. Symbol hints now skip test files, using the same test-path heuristic as the monograph node resolver (#441), so a symbol matched only in tests gets no hint; file-name hints rank non-test files first. -
The Bash destructive-ops gate no longer blocks commands that only mention a dangerous string (#427). The PreToolUse gate matched the raw command text, so
grep -rn "rm -rf" src/,git commit -m "document git push --force risks",echo 'DROP TABLE' > notes.txtand heredoc documentation were blocked. The patterns now run per command segment (split on&&,||,;,|,&and newlines outside quotes). Quoted literals that contain whitespace, and heredoc bodies, are masked only in segments whose command is on a short allowlist of commands that do not execute their arguments (grep/egrep/fgrep/rg/ag/ack,echo,printf,cat,tee,head,tail,wc,jq,yq,touch,mkdir,ls,git commit/tag/notes/log/show/grep/diff,gh issue/pr/api/release), and only when the rest of the pipeline is allowlisted too and, if anything writes a file, the whole command is. Options that run a program (printf -v,rg --pre,ack --pager,git grep -O) disqualify a segment. Every other segment is scanned with the quote characters removed and the text kept, sonpm exec -c '…',nodemon --exec "…"or any unknown runner still blocks. Assignment values (X="rm -rf ~"; $X) and quoted words without spaces ("rm" -rf ~) are never masked, and the whole raw command is scanned when a quote is unterminated, when there is a command substitution or a$Xcommand word, or when a word names a known string executor (bash -c,eval,xargs,ssh, interpreters, database clients such aspsql, git's-c/--exec/rebase/config). The monofence-ai layer on Bash now only warns (on stderr, logged to the security audit aswarn) unless the command also pipes downloaded content into a shell (curl … | sh,bash <(curl …),sh -c "$(curl …)"), which still blocks. -
The statusline renders in about 50 ms instead of about 220 ms (#429). Every render spawned
npm config get prefix(for the version), git (branch/status, remote, graph freshness, common dir),sqlite3(monograph and memory counts) andcurl(dashboard probe). Those results are now cached per project in.monomind/cache/statusline.json(git and the dashboard probe for 5 s, DB-derived counts for 30 s, version, remote and git dir for 10 min), and an expired segment is recomputed by the render that finds it expired, so a warm render only reads files. The rows and segments shown are unchanged. This covers both.claude/helpers/statusline.cjsand the scriptinitgenerates, which also drops its whole-output cache in~/.monomind/statusline-cache.json: that cache was keyed only by mode, so another project's statusline could show for up to 5 s. The generated script also importsspawnSyncagain, so its memory count shows instead of silently failing. -
The graph gate no longer blocks the first grep of a session; it adds a one-time reminder instead (#413). The PreToolUse Bash and Grep/Glob hooks blocked the first grep, rg or find of every session until a monograph tool was called. That cost a round trip, and in most blocked sessions the agent just ran grep again. The block also caught greps that were not code searches, such as
… | grep. The search now goes through, and the first source-code search of a session carries a reminder to trymonograph_query/monograph_suggestas PreToolUseadditionalContext. The reminder appears once per session, only when the graph is fresh and non-empty, and never for piped greps or searches over dependencies, build output, logs, docs/data files or paths outside the project. Theinitfallback hook-handler behaves the same way. Security gates still block as before. Also fixed: the node count the reminder quotes closed the hook's shared graph handle, which made every later[MONOGRAPH_HINT]lookup in that call fail. -
The per-prompt Second Brain lookup is cheaper and injects only strong matches; its background reindex uses the installed CLI on the docs only (#416). At a 0.35 floor with up to 3 excerpts, 2 of 27 injected excerpts were relevant (~180 tokens per prompt), and a hung dashboard server held every qualifying prompt for the full 900 ms timeout. The route hook now skips the POST when the pid in
.monomind/control.jsonis not alive, waits at most 250 ms, raises the semantic floor to 0.65 (the keyword fallback stays at 0.5) and injects one excerpt, plus a runner-up only when it scores within 0.03 of the top; nothing is injected when no hit clears the floor.relevanceFloorandinjectionLimitin.monomind/second-brain.jsonstill override. The session-start reindex rannpx -y monomind@latest doc ingest .(a different version than installed, over 2,402 files including package READMEs and duplicates); it now runs the project'snode_modules/.bin/monomind, amonomindon PATH or the monorepo's CLI entry, and skips when none exists, and it ingests onlydocs/,doc/and top-level*.md(dot-directories such as.agents,.gemini,.monomindand.mailare never walked; identical content shares one set of chunks). Only changes inside that scope trigger it.monomind doc ingestnow takes several paths and ingests them in turn. -
agent test --jsonreports crush and pi sign-in failures asauth(#473). Without credentials, crush says "No providers configured - please run 'crush' to set up a provider interactively." and pi says "No API key found for the selected model. Use /login to log into a provider via OAuth or API key." Both came back asstatus:"error"(runner-error). They are nowauth, and crush, pi and pi-rpc have alogin_hint(also shown byagent scan --json). Unknown-model and quota errors are classified as before. -
The dashboard server no longer duplicates itself, hangs on SIGTERM or grows to ~2 GB (#477). A second
server.mjsfor a project whose dashboard is already running (on the requested port, or on the port.monomind/control.jsonrecords, confirmed through/api/identity) now prints that dashboard's URL and exits 0; another project's server on the port is still skipped as before, andmonomind uireports the running dashboard instead of waiting. SIGTERM, SIGINT and SIGHUP now close every SSE stream (the mastermind and per-org streams were left open) and every socket, then exit 0, with a forced exit after 5 s. Sockets were never force-closed because the port-fallback loop removed Node's ownlisteninglistener, so the HTTP server tracked no connections andserver.close()waited on open keep-alive and SSE sockets indefinitely, leaving a process with no port that ignored SIGTERM. The memory growth came fromfs.watch({ recursive: true }), which Linux Node emulates with one inotify watch and one retainedfs.Statsper file: the.monomindwatcher alone held ~109,000 watches on one machine. Watchers now watch directories (capped at 2,000) instead of files: 100,000 files under.monomindnow cost 206 watches and 106 MB RSS at start, down from 100,208 watches and 312 MB. SSE clients that stop reading are dropped once 1 MB is queued, the per-run digest cache is capped at 64 runs, and nothing (watchers, the document sweep) starts before the port is bound. -
Each org role session gets its own TMPDIR (#480). Every role of an
org runshared the daemon's TMPDIR (the release org uses$HOME/mrg-tmp), so a baremktemp -dlanded in that shared root astmp.XXXXXXXXXX, and one role'srm -rf tmp.*there deleted other running roles' scratch too: 13 directories matched where 3 were meant on the 2.19.0 release run. Each role session (each task session withsession_scope: "task") now runs withTMPDIR,TMPandTEMPset to its own<base>/<org>-<role>-XXXXXX/(mode 0700), where<base>is the TMPDIR it had before, so the sandbox and file-tool roots still cover it. The runtime removes a task session's directory when its task closes, the rest when the role's session ends and at org stop, each by exact path and only with this org's and role's prefix; at start it sweeps only this org's directories whose owning process is gone. ATMPDIRthat a role-specific env overlay sets explicitly still wins. Every role's prompt now says its$TMPDIRis private and that cleanup globs must never run in a shared directory. -
monomind searchfinds documentsdoc ingestjust indexed, withoutsearch scan(#479). Which content typessearchlooks at comes from the directory fingerprint, and that is refreshed only bysearch scanor once it is a day old. A fingerprint taken before any document existed (asinitwrites one) kept the documents type off, sodoc searchfound a freshly ingested file andsearchdid not. The documents type was also off in a tree where documents are under 10% of the files, even right after a scan. Now, when the project's doc store has live documents,searchturns the documents type on for that run. This covers everything that writes the project store:doc ingest, the MCPknowledge_ingesttool, and the session-start auto-ingest. There is no rescan and the saved fingerprint is not rewritten, so an unchanged store costs one read ofdoc-metadata.jsonl.searchstill reads documents from the working tree, so a file ingested from outside it, or into the global brain or a profile store, is found bydoc searchonly.search --helpnow says whatsearch scanis still needed for: other content types added since the last scan. -
config getprints object and array values instead of[object Object](#478).monomind config get memoryprintedmemory = [object Object]because the value went straight into a template string. An object or array value is now printed as indented JSON afterkey =; scalars print as before, and--format jsonwas already correct. The full-config table (config getwith no key) printed arrays withString(), so an array of objects showed[object Object]; arrays there are now compact JSON (["a","b"],[]).
npm: https://www.npmjs.com/package/monomind/v/2.20.0 · https://www.npmjs.com/package/@monoes/monomindcli/v/2.20.0