feat(hermes): prove the message boundary of a one-shot turn (issue #5 proposal 1) - #8
Merged
Merged
Conversation
…proposal 1) The README promises the "final active message" from state.db, but nothing bounded WHICH message is the turn. Now every row in the matched session must carry a timestamp inside the invocation window — a row outside it (late finalization write, reused/continued session, concurrent writer) fails closed. The turn remains the final ACTIVE text-bearing assistant message; interim active drafts and inactive ghost tails are documented as normal one-shot noise, while an active user row after the final assistant message (a continued session) fails closed. The accepted span (first/final message id, row count) is recorded in the proof as message_boundary. Canary: fixture with a later ACTIVE assistant message outside the window → turn rejected, no ledger commit (plus the continued-session and unusable-timestamp matrix rows).
askclaw-vesper
force-pushed
the
feat/hermes-message-boundary
branch
from
August 20, 2026 05:33
a3ded15 to
91418ef
Compare
There was a problem hiding this comment.
Confidence Score: 4/5
Summary
Adds fail-closed message-boundary proof to the Hermes adapter (window bound, continuation bound, recorded span) with strong test coverage; verify passes. Low risk, but stacked on #7 and one tail-row class escapes the fail-closed claim.
Important Files Changed
| File | Overview |
|---|---|
| src/multi_agent_dialogue/adapters/hermes.py | Window and continuation boundary checks; message_boundary span added to proof |
| examples/fakes/bin/fake-hermes | New FAKE_LATE_ASSISTANT and FAKE_FOLLOWUP_USER fixture knobs |
| tests/test_real_contracts.py | Canary, matrix, and proof-record tests for the boundary |
| docs/technical-reference.md | Documents boundary rules and accepted one-shot noise |
| src/multi_agent_dialogue/config.py | From stacked PR #7: evidence_versions parsing and support set |
| src/multi_agent_dialogue/evidence.py | From stacked PR #7: strict version acceptance validation |
| src/multi_agent_dialogue/engine.py | From stacked PR #7: passes accepted_versions to validation |
| src/multi_agent_dialogue/adapters/base.py | From stacked PR #7: imports EVIDENCE_VERSION from evidence |
| schemas/protocol.schema.json | From stacked PR #7: evidence_versions schema property |
| tests/test_config.py | From stacked PR #7: evidence_versions parse tests |
| tests/test_evidence.py | From stacked PR #7: version validation tests |
Review follow-up: the tail bound now rejects ANY active row after the final assistant message (tool/system/empty-stub included), not just user follow-ups; the unusable-timestamp raise chains properly; the dead empty-session fallback is gone; message_count renamed to session_row_count so the recorded span fields agree (the count covers every session row, including an inactive ghost tail).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements issue #5, proposal 1 (Hermes message-boundary check). Stacked on #7 — merge that first; this branch will be updated.
Problem
The README promises the "final active message" from
state.db, but nothing bounded which message is the turn: the adapter took the max-id active assistant row, absorbing late or foreign writes silently.What changes (adapter, fail-closed)
userrow after it means the session continued beyond this turn: completion refused.message_boundary(first_message_id,final_message_id,message_count) so review sees exactly which rows bounded the turn.Explicit exception rules (documented in the technical reference)
Normal one-shot noise that stays accepted: interim active assistant drafts before the final message (tool narration, superseded text — the contract fixture itself writes one) and inactive tail rows (compaction ghosts). Everything else about the boundary fails closed.
Canary (from the proposal)
Fixture with a later ACTIVE assistant message (timestamped outside the window) → turn rejected,
ProtocolError, no ledger commit,completed_turnsempty (test_late_assistant_message_fails_closed). Matrix additions: continued-session user row, unusable timestamp, boundary-record contents.Tests
FAKE_LATE_ASSISTANT,FAKE_FOLLOWUP_USER.scripts/verify.py→{"ok": true}.