Skip to content

fix(buzz): latch DM classification on p-tag when channel metadata says DM - #77901

Open
solovision24 wants to merge 20 commits into
NousResearch:mainfrom
solovision24:agent/halo-buzz-dm-latch-t_bc8d998b
Open

fix(buzz): latch DM classification on p-tag when channel metadata says DM#77901
solovision24 wants to merge 20 commits into
NousResearch:mainfrom
solovision24:agent/halo-buzz-dm-latch-t_bc8d998b

Conversation

@solovision24

Copy link
Copy Markdown

Summary

Fixes a DM session-split in the Buzz platform adapter: a direct message whose prose names the agent (e.g. "Hey Orion, do you know why this job is progressing?") was excluded from DM detection because it visibly mentions the agent, so the first message dispatched as chat_type=group. A later un-mentioned message in the same DM latched chat_type=dm — splitting one conversation into two sessions (session keys embed chat_type) with no shared history.

Root cause

_is_direct_message_event required the content to NOT visibly mention the agent. In a real DM, users routinely name the agent in prose without a typed @mention. The p-tag is structural recipient addressing; the visible-mention exclusion exists only to protect real community channels (which p-tag us solely on typed mentions) from reclassification.

Fix

When channel metadata already marks the conversation as a DM (name == "DM" and empty description), the structural p-tag alone latches chat_type=dm. The visible-mention exclusion still applies to metadata-less conversations and real named channels, preserving the original protection.

Tests

Adds plugins/platforms/buzz/test_dm_classification.py with 4 regression tests:

  • DM prose-naming the agent latches as dm (the bug)
  • Real channel with typed mention stays group
  • Mentionless DM still latches
  • Metadata-less channel with typed mention is not reclassified

Verified old code fails the first test; new code passes all 4.

Kanban Task

https://mc.solobot.cloud/tasks?task=t_bc8d998b

solovision24 and others added 20 commits July 31, 2026 08:14
…026.7.20 upgrade

- agent/conversation_loop.py: prioritise structured reasoning fields over
  inline-think content for tool_progress_callback relay
- ui-tui appLayout.tsx: drop stale inputHeight box sizing (auto-size from
  rendered content instead)
- ui-tui textInput.tsx: cancel pending fast-echo parent update on submit
Merged after independent verification: 54 focused lifecycle/tool/core tests passed, compileall and git diff --check passed, and the PR is limited to the upstream-compatible Hermes baseline.
* fix(kanban): allow requeued review workers past PR guard

* fix(kanban): preserve review routing after crash requeue

* fix(kanban): preserve native review lane on crash

* fix(kanban): apply retry guards to native reviews

* fix(kanban): guard native review respawns during cooldown

---------

Co-authored-by: SoLo <solo@SoLoBot>
Co-authored-by: SoLo <solo@SoLoBot>
* fix(kanban): restore native GitHub PR ingest

* fix(kanban): restore GitHub PR lifecycle safeguards

---------

Co-authored-by: SoLo <solo@SoLoBot>
* feat(kanban): enforce native review lifecycle

* fix(kanban): authorize review changes from active review runs

---------

Co-authored-by: SoLo <solo@SoLoBot>
* feat(kanban): enforce native review lifecycle

* fix(kanban): authorize review changes from active review runs

* ci: scope contributor checks to the PR base

---------

Co-authored-by: SoLo <solo@SoLoBot>
* fix(kanban): replay native review convergence on current base

* fix(kanban): restore same-card review remediation lifecycle

* fix(kanban): replay webhook review convergence on approved base

---------

Co-authored-by: SoLo <solo@SoLoBot>
* fix(kanban): finalize claimed webhook review runs

* fix(kanban): correct native review handoff comment

---------

Co-authored-by: SoLo <solo@SoLoBot>
* fix(kanban): hard-gate changes-required review completion

* fix(kanban): harden review remediation gates

* fix(kanban): bind review approval to immutable head

* fix(kanban): expose ready review remediation handoff

* fix(kanban): authorize remediation dependency exception

* fix(kanban): secure remediation handoff identity

* fix(kanban): harden remediation key contract

* fix(kanban): reject pre-seeded remediation keys

---------

Co-authored-by: SoLo <solo@SoLoBot>
* fix(kanban): repair same-card review branch base

* fix(kanban): ignore archived prior review remediation keys

---------

Co-authored-by: SoLo <solo@SoLoBot>
…tch probe contract

Incident 2026-08-03: corrupt messages b-tree / FTS trees made kanban
workers fail their first canonical transcript write (session_persistence_
failed -> 'session storage could not be written') and drain the fleet via
the two-failure circuit breaker.

- New regression tests (tests/state/test_state_db_corruption_worker_drain.py):
  9 pass today, reproducing the write failure against a real corrupted
  messages b-tree, proving the corruption is silent to plain reads, pinning
  the session_persistence_failed message contract, and showing FTS classes
  self-heal while the messages b-tree class does not.
  1 fails on purpose: test_pre_dispatch_state_db_probe_exists_in_kanban_dispatch
  pins the contract for the pre-dispatch state.db health probe to implement
  in hermes_cli/kanban_db.py (delegate to hermes_state._db_opens_cleanly).
- docs/design/state-db-corruption-worker-drain.md: exact write-path trace,
  corruption classes + current self-heal behavior, detector/repair
  machinery, pre-dispatch probe contract, recovery/restart paths.

No production behavior changed; no PR to official Hermes.
Alert when Todo/Blocked work exists but no runnable worker remains
because every owner profile is gated — quarantined/unhealthy session
store or tripped two-failure circuit breaker. This is the 2026-08-03
"silent fleet drain" signal: after the breaker trips every task goes
blocked and the board looks idle even though it is drained.

- New hermes_cli/kanban_health.py: read-only check_queue_drain() +
  QueueDrainReport + quarantine provider seam (task-1 event stream
  scan by default, register_quarantine_provider for richer providers).
- Gateway dispatcher watcher runs the check every tick, rate-limited
  to one ERROR alert per board per 5 min.
- `hermes kanban health` CLI subcommand (exit 1 when alerting) for
  on-demand checks / cron watchdogs; mirrored into the --force daemon.
- 14 tests: all-gated (quarantine / breaker / mixed) fires, runnable
  worker suppresses, event-stream quarantine detection, CLI exit codes.

Local-only change; no external PR.
Never spawn a worker against an unhealthy profile store. A worker
pointed at a corrupt state.db opens fine, fails its first canonical
transcript write (session_persistence_failed), and drains the fleet
through the failure circuit breaker (incident 2026-08-03).

- Add hermes_cli.kanban_db.pre_dispatch_state_db_probe(profile_name)
  -> Optional[str], resolving the profile's HERMES_HOME state.db and
  delegating to hermes_state._db_opens_cleanly (integrity_check, FTS
  read probes, rolled-back canonical write probe). Non-destructive;
  missing state.db is healthy.
- dispatch_once consults it per assignee (memoized per profile per
  tick) before _default_spawn: on failure, ready tasks are blocked
  (kind=capability) with the high-signal diagnostic
  'profile <name> store unhealthy: <error>; worker blocked'; review
  tasks are skipped without claiming and a 'quarantined' event records
  the reason. Store is never replaced/deleted; timestamped backups are
  the recovery path.
- detect_crashed_workers probes the assignee's store for the low-signal
  'pid N not alive' class and records the store diagnostic instead when
  the store is unhealthy.
- Tests: tests/hermes_cli/test_kanban_store_quarantine.py (probe,
  quarantine gate, healthy-profile dispatch, dry-run, review lane,
  crash diagnostic); task-0 regression tests now pass.
…OVERY.md

End-to-end verification of the 2026-08-03 state.db hardening epic exposed
an integration gap: task 1's quarantine gate blocked tasks via `blocked`
events, but task 2's queue-drain alert (kanban_health) only scans
`profile_quarantined` / `profile_store_healthy` events — and nothing
registered the richer provider seam — so in production the alert could
never fire from a real quarantine.

Fix: the pre-dispatch gate now emits the contract events itself, once per
profile per tick (deduped via a per-tick set, separate from the probe
memo):
- `profile_quarantined` when a task is blocked / review-skipped (payload
  carries profile, reason=store_unhealthy, blocker detail, db_path,
  sqlite error)
- `profile_store_healthy` when a previously-quarantined profile's store
  heals and the probe passes on a later tick (uses kanban_health's
  get_quarantine_state as the single source of truth, lazy import to
  avoid a cycle)

Verified with a full incident simulation on real corrupted stores (all
checks pass): healthy profiles dispatch; corrupt default/quill/orion are
quarantined; no worker spawns; crash diagnostic is store-unhealthy not
pid-not-alive; queue-drain alert fires when the whole fleet is gated and
clears when a store heals; no state.db is ever replaced/deleted.

Also adds RECOVERY.md (safe offline recovery guidance: timestamped backup
first, integrity/foreign_key checks on the backup, FTS rebuild / REINDEX
on a copy not the live DB, unreadable rows preserved in a separate backup,
restore path, post-restore unblock + probe verification) and documents
the event wiring in docs/design/state-db-corruption-worker-drain.md.

Tests: 47 relevant tests pass (quarantine 17, alert 14, state-db drain 10,
malformed repair + FTS rebuild 6); full hermes_cli -k kanban suite 248
pass. Four new event-contract tests in
tests/hermes_cli/test_kanban_store_quarantine.py.

Local-only commit: no PR to official Hermes opened — per task policy no
official Hermes PR without SoLo's explicit approval, and no GitHub
credential exists on this host for push (same blocker as parent tasks).
…s DM

A DM whose prose names the agent ("Hey Orion, ...") was excluded from
DM detection because it visibly mentions the agent, so the first message
dispatched as chat_type=group and a later un-mentioned message latched
chat_type=dm — splitting one DM into two sessions with no shared history
(session keys embed chat_type).

When channel metadata already marks the conversation as a DM (name ==
"DM", empty description), the structural p-tag alone latches it.  The
visible-mention exclusion still protects metadata-less conversations and
real community channels that p-tag us on typed mentions.

Adds regression tests reproducing the split (t_bc8d998b).
@alt-glitch alt-glitch added invalid This doesn't seem right P3 Low — cosmetic, nice to have comp/plugins Plugin system and bundled plugins labels Aug 3, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

This branch includes extensive unrelated agent, Kanban, auth, CLI, and TUI changes alongside the Buzz DM fix. Please rebase and submit only the focused Buzz change for review.

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

Labels

comp/plugins Plugin system and bundled plugins invalid This doesn't seem right P3 Low — cosmetic, nice to have

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants