Skip to content

fix(hive): skip archived agents' outboxes on ordinary router ticks - #836

Open
ugurcany wants to merge 1 commit into
HarnessMD:mainfrom
ugurcany:fix/811-router-walk
Open

ugurcany wants to merge 1 commit into
HarnessMD:mainfrom
ugurcany:fix/811-router-walk

Conversation

@ugurcany

@ugurcany ugurcany commented Nov 9, 2026 •

Copy link
Copy Markdown

What & why

The hive router (routeOnce() in src/main/hive.ts, run every 1.5 s by startRouter) did a synchronous walk of every folder under hive/agents and every outbox, archived agents included, on the Electron main thread. Issue #811 measured that walk at 68 to 79 ms per tick over 1,363 folders, and showed it is the 1.5 s series of cursor stalls: moving the archived folders out cut the stalls from 33 to 10 in 30 s and the 1.5 s rhythm disappeared.

This change makes ordinary ticks skip agents that registry.json marks archived. The archived ids are cached and the registry is re-parsed only when its mtime or size changes, so a tick costs one statSync on top of the folder listing.

Messages in an archived agent's outbox are still routed, on a full sweep that walks every outbox as before:

  • On the tick after registry.json changes. Archiving an agent (setArchived) writes the registry, so whatever that agent left in its outbox just before its tab closed is drained on the next tick.
  • Every ROUTER_FULL_SWEEP_EVERY ticks (40, so 60 s at the default 1.5 s interval), as a backstop for anything written into an archived outbox later, for example by a process the agent left running. Worst case such a message waits 60 s instead of 1.5 s.

Agent folders that are not in the registry are still walked every tick, as before.

Not fixed here, because the code is not in this repository: the puck window's setIgnoreMouseEvents forward hook, and the 3 s series the issue's follow-up attributes to startConnectionRequests() / processAgentFileRequests(). Both exist only in the packaged 0.5.7 build. I also checked WORKER_TICK_MS = 1500 in src/main/index.ts: that tick only looks at live workers and the spawn-requests folder, not at every agent folder, so it does not have this problem.

Refs #811 (the public part only; the 3 s series stays open).

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Docs
  • Build / CI

Evidence

Performance change, so the evidence is the measurement before and after on the same machine. The benchmark builds a fake hive with 1,363 agent folders, 69 live and 1,294 archived in registry.json (the shape from the issue), then times 40 calls to routeOnce(). The first call is a full sweep in both cases; the figures below are the other 39. Two runs each, Windows 11, Node 24.12.

Before

Before: router tick median 220-313 ms over 1,363 folders; new test fails 14 !== 2

folders 1363, live 69, ticks 40
first tick 205.2 ms; later ticks median 219.51 ms, min 164.63, max 1335.40
folders 1363, live 69, ticks 40
first tick 323.4 ms; later ticks median 312.88 ms, min 192.84, max 956.88

New test on main:

not ok 1 - an ordinary router tick skips archived agents' outboxes
  expected: 2
  actual: 14
# pass 2
# fail 1

After

After: router tick median 9-11 ms on the same machine; new test passes 3 of 3

folders 1363, live 69, ticks 40
first tick 186.0 ms; later ticks median 9.05 ms, min 7.63, max 14.71
folders 1363, live 69, ticks 40
first tick 159.7 ms; later ticks median 10.98 ms, min 8.06, max 14.07

New test on this branch:

# pass 3
# fail 0

The absolute numbers are higher than the issue's 68 to 79 ms because the fake hive sits in the temp folder, which the antivirus scans on this machine; the ratio is the point. What is left per tick (about 10 ms here) is the one readdirSync of hive/agents plus the live agents' outboxes.

How I tested it

  • OS: Windows 11 Pro
  • Steps:
    • Added test/hive-router-archived-walk.test.cjs (its temp folder name contains a space). It checks that an ordinary tick visits only live outboxes, that a message left by an agent that is then archived is routed on the next tick, and that a late write into an archived outbox is routed by the periodic full sweep. Ran it on main (1 of 3 fails, above) and on this branch (3 of 3 pass).
    • npm run typecheck: passes.
    • npm run test:focused: 965 tests, 936 pass, 15 fail, 14 skipped. The 15 failures are in 11 files (worktree-deps, telemetry-message-count, hooks-socket, quit-sweep.electron, restart-terminal-preserve, and others) and the same 15 fail on unchanged main on this machine: Windows symlink permissions and a node-pty that was not rebuilt (npm install --ignore-scripts, because the native rebuild fails here). None touch the router.
    • npm run build: succeeds.
    • Ran the benchmark above against main and this branch.

Agent self-review: checked cross-platform paths (only join), paths with spaces (test uses one), provider neutrality (the router does not look at the provider), and the hot path (one statSync per tick; the registry is parsed only when it changes). One thing considered and left: the registry cache keys on mtime plus size, so two writes within the same mtime tick that keep the same size would be missed until the next 60 s sweep. That only delays delivery, it does not lose a message.

Credit (optional)

Discord:

X:

Checklist

  • Before and after evidence is attached above, under both headings.
  • npm run typecheck passes.
  • npm run test:focused passes. (Passes except 15 failures that also fail on main on Windows; see above.)
  • npm run build succeeds.
  • This PR is one change. Unrelated fixes belong in their own PR.
  • I read the diff myself before opening this, and there is no debug output,
    commented-out code, or unrelated formatting churn in it.
  • Any new UI derives from DESIGN.md / tokens.ts — no ad-hoc colors,
    spacing, or fonts. (No UI change.)
  • If I added art, it's my own or compatibly licensed, and listed in
    ATTRIBUTION.md. (No art.)

🤖 Generated with Claude Code

routeOnce() walked every folder under hive/agents and every outbox on the
main thread each 1.5 s tick, archived agents included. On a floor with over
a thousand archived temps that walk froze the cursor every 1.5 s (HarnessMD#811).

The router now reads the archived ids from registry.json, re-parsing only
when the file's mtime or size changes, and skips those outboxes on ordinary
ticks. It still walks them on a full sweep: on the tick after registry.json
changes, so an agent's last messages are drained after it is archived, and
every ROUTER_FULL_SWEEP_EVERY ticks (60 s at the default interval) as a
backstop for anything written into an archived outbox later.

Refs HarnessMD#811

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

✅ Evidence received. Before and after are both attached. Thanks — this is what makes a PR reviewable in one pass.

ugurcany added a commit to ugurcany/munder-difflin that referenced this pull request Nov 9, 2026
ugurcany added a commit to ugurcany/munder-difflin that referenced this pull request Nov 9, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant