Skip to content

v2.15.6

Choose a tag to compare

@nokhodian nokhodian released this 22 Sep 15:45
· 1608 commits to main since this release

[2.15.6] — 2026-09-22

Added

  • policy.sandbox.denyWrite for org roles. Paths listed there are read-only for the role's shell (OS sandbox) and its file tools; relative paths resolve against the org root, so ["."] keeps a role from writing anywhere in the checkout. The release org now uses it — with mode: "required" — for its QA and audit roles, after a QA role ran cleanup --force from the main checkout during a release run.

Changed

  • monomind cleanup --force keeps user data unless you also pass --purge-data. Memory stores (data/memory, MONOMIND_MEMORY_PATH, memory/), .monomind/org-memory, .monomind/knowledge, .monomind/orgs, .monomind/org-skills, backups, the monograph database and any other *.db file are listed as kept by --force alone; --force --purge-data removes them too, provided they are not git-tracked. The preview (cleanup without --force) runs the same plan, so it lists exactly what --force will remove or edit, followed by everything it keeps and why.
  • Completion evidence is checked against every local worktree and branch, not just the org workspace. With run_config.completion_evidence on, org_task_done used to refuse any evidence whose headSha was not the org workspace's HEAD — so an org that builds in a release or per-task worktree (the usual shape) could never close a task honestly. Evidence may now be pinned to the current HEAD of any worktree of the repository or the tip of any local branch, and may name its worktree to pin the check to that worktree's HEAD exactly. A commit that is no longer the head of any local work is still refused as stale, and the refusal now lists the current heads.
  • Completion evidence checks may declare the exit code they expect. On the release org's first run, checks whose correct outcome is non-zero — a branch-protection GET that 404s, git config --get of an unset key, agent exec --timeout 1s → 124 — were refused, and roles "fixed" them with || true, erasing the exit code the gate checks. Each check now takes an optional expectExit; it passes iff exitCode === (expectExit ?? 0), a mismatch refusal names the expected and actual codes, and the review packet and the task's recorded result show exit 1 (expected 1).
  • Evidence refusals say how to close a report task and where to pin out-of-repo checks. A QA task that finishes by reporting failures could not close, because the failing command it found went into checks. The gate still requires every check to pass; the refusal and the org_task_done description now say that such a task's acceptance commands prove the report exists (e.g. test -s <report>) and its failures go in result and to the coordinator. Evidence whose worktree is a scratch dir (e.g. an installed tarball) is still refused; the refusal now says to pin headSha/worktree to the git worktree the tested artifact was built from.
  • monomind --version now refreshes a stale update cache in the background. It returns before the startup update check runs, so until now it never refreshed the cache its tagline reads — on a missing or stale cache it stayed silent about new releases until some other command ran. When the cache is missing or older than the 24h check interval, --version prints its line exactly as before, then reserves the check slot and starts a detached, unref'd child that prints nothing, fetches the latest versions (5s per-request timeout, 20s hard cap, silent when offline) and rewrites the cache, so the next --version is accurate. It uses the startup check's own gate — CI/CONTINUOUS_INTEGRATION, MONOMIND_AUTO_UPDATE=false, the 24h interval and the daily cap — and --no-update; reserving the slot stamps lastCheck first, so a refresh that is running or just ran blocks the next one. --version --json never spawns. Cost on the run that spawns: roughly 5-10 ms.

Fixed

  • monomind cleanup --force deleted git-tracked files and the project's memory. It removed a fixed list of paths wholesale — .claude/, .agents/, .gemini/, .opencode/, .codex/, .kimi-code/, data/, memory/, AGENTS.md, GEMINI.md, opencode.json, .mcp.json — without checking that monomind had created them. Run in a real repository it deleted about 1000 git-tracked files (including hand-written AGENTS.md/GEMINI.md, which many projects own themselves) and wiped the memory store, org memory, knowledge index and monograph database. Cleanup now builds one ownership-checked plan. It never deletes or edits a git-tracked file (a directory holding tracked files keeps them; only its untracked monomind content goes), and it refuses to delete anything if a git repository is present but git ls-files fails. It deletes a path only when monomind demonstrably owns it: untracked .monomind/ runtime state and monomind.config.json, entries listed in .monomind/init-manifest.json, monomind* files in agent-tool directories, a GEMINI.md still carrying init's title line, and files whose whole content is monomind marker blocks. From a file that mixes user content with a monomind block (AGENTS.md, CLAUDE.md, GEMINI.md) or a monomind server entry (.mcp.json, opencode.json), only the monomind part is removed. Everything else, including .claude/settings.json, is kept and listed with the reason. cleanup --force also refuses to run in monomind's own source checkout (root package.json named monomind with packages/@monomind/cli present).
  • cleanup --force killed live agent processes when Claude Code ran as a container's pid 1. Deciding orphan-hood by pid 1's name went wrong both ways: a systemd/init allow-list left real orphans running under tini or bwrap, and the replacement "pid 1 is a shell" deny-list SIGTERMed the live SDK children of a node .../claude pid 1, whose ppid is 1 (reproduced 3/3 with real processes in a PID namespace). A claude-agent-sdk process is now reaped only when ownership says it is an orphan. It must not be the invoking process, one of its ancestors or one of its descendants. It must share neither the invoking session nor its process group. No live claude/monomind process may sit up its parent chain. And it must no longer be in its parent's session, which is what happens when init, systemd --user, tini, bwrap or any other subreaper adopts it. On Linux this is read from /proc (ppid, process group, session and start time; the start time also guards against a recycled PID). macOS keeps the ps fallback, now with the process-group check, and Windows still never reaps.
  • An org_task_done call with no evidence object no longer spends an evidence attempt. On the release org's first run, 4 of 6 tasks lost an attempt toward max_evidence_attempts because the role's first call simply omitted evidence — a formatting slip, not a failed proof — leaving them one refusal from escalation. Such a call is still refused and requeued, and the returned notice says it did not count; failed checks, a stale sha and an unknown worktree still count.
  • The no-progress alarm no longer fires for a role that has nothing to do. role "publisher" has been running for 30m without a single bus event — it is hooked but producing nothing was emitted for a role with no task whose process had exited on session_idle_exit_ms and was parked waiting for mail. The alarm now only considers a role with a running task, undelivered mail, or a turn in progress (not parked on its mailbox); a role with a running task or pending mail that emits nothing still trips it.
  • monodesign URL detection intermittently failed with "monobrowse: CDP connection closed" when browsers launched concurrently. Each detection launch picked its own CDP port (random in 9520-9899, "free" per a probe made before Chrome bound it), so two concurrent launches — e.g. test files run in parallel by node --test — could pick the same port. The second Chrome then did not fail: it logged bind() failed: Address already in use, silently listened on [::1]:<port> instead, and monobrowse (polling 127.0.0.1:<port>) accepted the other launcher's Chrome as its own. When that owner closed its browser, this launch died mid-connect, and its own Chrome was left running. launchBrowser({ port: 0, userDataDir }) (monobrowse 1.0.18) now has Chrome take a kernel-assigned port and reads it from DevToolsActivePort in its own profile directory, so the endpoint is always the process it spawned; monodesign 1.2.18 uses it for every launch without a forced MONODESIGN_MONOBROWSE_PORT.
  • Two concurrent monomind browse open calls (or any two port-less launchBrowser() callers) reliably failed one of the two with "Chrome exited before the CDP endpoint opened on port 9222 (code=21, signal=null)". The port-scan probed each candidate with a TCP connect before spawning Chrome on it, which only proves nothing was listening a moment ago — two racing callers could both see the same candidate as free and both spawn Chrome there, so one lost the real bind and its launch threw instead of moving on. A launch that loses this race now checks whether something else is now listening on that candidate and, if so, retries the next one exactly like an already-occupied port, instead of failing outright. Also fixed a related bug the retry uncovered: process tracking used for closeBrowser()'s kill fallback recorded a spawned pid before confirming it had actually bound the port, so a racing loser's about-to-exit pid could overwrite the winner's entry; tracking now happens only once, at confirmed success.
  • monomind search <query> (and search --type code) printed a bare "No results found." on a fresh project with no indication of what to do next. The code capability searches the monograph knowledge graph's on-disk database, which only the explicit monomind monograph build populates — the directory scan search auto-runs on first use just records that code files are present, it doesn't index their content. So a fresh project legitimately returns zero code results, indistinguishable from "no matches" on the user's side. When the code capability is active, results are empty, any --type filter is unset or code, and the index database doesn't exist yet, search now prints a one-line hint: "Hint: the code index has not been built yet. Run monomind monograph build and search again."

npm: monomind@2.15.6, @monoes/monomindcli@2.15.6, @monoes/monodesign@1.2.18, @monoes/monobrowse@1.0.19