← J&M Labs

Weekly Hermes PR — how we pick, comment, and ship

August 12, 2026 · updated later that evening · James Meadlock + Milo · week of August 10

What this is. A process note for the weekly NousResearch/hermes-agent contribution loop — not a scoreboard. Goal: one clean, mergeable fix without stepping on other contributors.
Evening update, August 12. Next scout pick #84870 (stale session-list root after /new) is real, but it sits next to three open visibility PRs. Opus 5: comment only. Posted this comment. No second coding PR while #84867 is unanswered.
Repo
hermes-agent
Shipped this week
#84867 open
Social-only
Prior FD PR
#74304 closed Aug 11

Pace is deliberate: about an hour a day, model contributor rather than high-volume. Hermes is the right on-ramp — we run it daily, so a scoped bug is cheap to reproduce and hard to fake. This week’s loop was mostly triage discipline, then one real PR, then a second candidate that stayed comment-only.

1. Portfolio check first

Before opening anything new, inventory open authored PRs. This session: no open upstream Hermes PRs. The SessionDB FD work (#74304) had closed August 11. Clean slate for a weekly bite.

Fork-only leftovers (Agent-Reach self-PR, local drafts) stay out of the weekly queue until they have a real upstream path.

2. Scout with a hard filter

Hermes issue traffic is high. Fresh P2/P3 bugs get scooped within hours. The filter we actually used:

  1. Open issue, English, recent activity
  2. Root cause named in the body (or clear in code)
  3. No open competing PR for the same fix shape
  4. Not stampeded “banner/update/xAI/Grok” piles unless free
  5. Contained: preferably one function / one contract + a regression test
  6. Fits how we run Hermes (macOS, profiles, gateway, agent loop)

Same-day examples that failed the filter:

IssueWhy skip
Grok 4.6 xhigh / priorityTwo open PRs already
cron attach_to_sessionTwo open PRs
backup locked-DB hangPR open
MCP lazy dispatchPR open
xAI aux 403Scooped mid-session
SSH false “1 commit behind”Scooped + banner PR pile
Rule: if gh pr list --search ISSUE returns a live PR with the right shape, do not open a parallel one. Link and move on.

3. Candidate A — launchd restart budget (#84716)

#84716 looked perfect on paper: macOS launchd hermes gateway restart waits only restart_drain_timeout (default 0), while systemd uses restart_after_turn_timeout + drain + headroom (default ~21615s). Fresh shells miss SIGUSR1 (ancestor-only helper) and force-interrupt in-flight cron.

Measured on upstream/main @ d2c6af3aa. Then the ownership check:

Plan + Opus 5 review

We wrote a measured plan (defaults, control flow, competing PRs), then ran a Nous consult with anthropic/claude-opus-5 (empty toolsets, usage file, hashed transcript). Verdict: APPROVE-WITH-NITS — do not open a competing PR this week.

What we did instead (August 12):

  1. Comment on the issue with root cause + link to #77512 (receipt)
  2. One-liner on the existing PR: “Fresh receipts on #84716; same root cause.”
  3. No branch. Salvage only if silence holds ~7 days or the author hands it off.
Highest credit-per-minute action here was triage that links the issue to the existing fix, not code. Opening a same-scope PR would have been the awkward path.

4. Candidate B — invalid-JSON recovery by call id (#84698)

#84698: invalid-JSON recovery stored (tool_name, error). Two parallel calls to the same tool — one valid JSON, one broken — poisoned the valid sibling and skipped execution.

Research that changed the fix shape

Two options went to Opus:

OptionBehaviorCall
A — attribution onlyKey by index/id; still Skip valid siblingsReject as primary
B — mixed executeKey by tool_call_id; execute valid; error only broken; all-invalid keeps 3× retryShip this

Opus: APPROVE-WITH-NITS — B, not A. Also: rekey the truncation scan by id in the same commit (same bug class); require full-loop side-effect tests, not helper-only unit tests.

Social call on wait time

Default advice was claim + 24h. We shortened it: a self-closed unmerged PR with zero discussion is abandonment, not “stepping away for lunch.” Courtesy is the claim line (“unless you’re still on it”); calendar delay would have been theater.

What we shipped

  1. Claim on the issue (receipt)
  2. Branch from upstream/main
  3. RED full-loop tests (mock provider, streaming disabled so bad JSON reaches the recovery path)
  4. Implement B in conversation_loop.py
  5. Green: new suite + empty-name suite = 10 passed
  6. PR: #84867 (+479 / −45) — Fixes #84698, credits #84759
fix(agent): key invalid-JSON tool recovery by call id, execute valid siblings

- Key by tool_call_id (never name)
- Mixed valid+invalid JSON → execute valid siblings
- All-invalid → keep 3× API retry + id-keyed recovery
- Truncation scan rekeyed by id; hard-stop semantics unchanged

5. Candidate C — stale list root after /new (#84870)

After #84867 landed as the week’s coding PR, we scouted again. Top free pick: #84870 (P2, 0 comments). Gateway /new chains a new session with parent_session_id and end_reason=session_reset. hermes sessions list / the desktop sidebar still show the stale lineage root (old title, old timestamp).

Measured on upstream/main @ e4f480510. Three sites all follow compression only:

SiteWhat it does today
list_sessions_rich CTEDoes not walk /new children
Python projectionSkips non-compression roots
get_compression_tip()Same compression-only walk

_LISTABLE_CHILD_SQL only admits roots + branches, so the live tip is a hidden child. No data loss — search still finds the messages.

Same-week cluster (different design)

Three open PRs already own the other axis: make reset children independently listable, not project the parent row forward to the tip.

PRAxis
#83987Gateway-boundary children listable
#84198Same visibility, session_reset only
#84009_reset_from marker + list predicate

If those merge as written, the reporter may see two rows (stale root + tip) — arguably worse. Projection is then more justified, as a follow-up, after a maintainer picks UX.

Plan + Opus 5

Measured brief (CTE lines, competing PRs, #84867 still unreviewed) → Nous anthropic/claude-opus-5. Verdict: APPROVE Option A — comment only. Do not open a fourth PR in a contested session-list pile while #84867 has no reviews.

Posted August 12 (evening CDT) on #84870 only — no traffic-cop comments on the three PRs:

issuecomment-5274260584

Repro confirmed on main @ e4f4805. List projection only follows
end_reason = 'compression' — CTE, Python projection, and
get_compression_tip() all agree — so a session_reset tip is a hidden
child and the row keeps the root's title/time.

Adjacent open PRs (#83987, #84198, #84009) make reset children
independently listable rather than projecting the parent row forward.
If one lands, this likely presents as two rows — stale root and tip.

Maintainer call before anyone writes code: one row per lineage
projected to the tip, or parent + child both listed?
Same social rule as #84716, different reason: there the existing PR already had the right shape. Here the adjacent PRs have a different shape. Either way, we don’t compete this week.

6. Process checklist we will reuse

StepDoDon’t
Portfolio List open authored PRs first Stack parallel unknown-scope PRs
Scout Filter for free + clear root cause Trust titles; stampede piles
Ownership gh pr list --search N + timeline Assume “open issue = free”
Plan Measured defaults + grepped code paths Vibe-based “one-liner” guesses
Second opinion Opus with measured brief + NEVER column Fabricated numbers in the brief
Social Claim issue; link prior art; stand-down line Compete with green same-shape PRs
Code TDD full-loop when behavior is the bug Helper-only tests that pass on broken main
Wait clocks Self-closed abandon → claim + code Artificial 24h when the author already left
Adjacent shape Comment the issue; name the UX fork; sit Fourth PR in a contested cluster; traffic-cop the other PRs

7. Open threads

Related earlier process writeup: A first OSS bug fix with an AI coach · SessionDB FD notes: Hermes SessionDB FD leak PR