Repository navigation
feat(discord): opt-in raw-reaction journal for durable triage state - #129
Merged
Merged
Conversation
When discord.reaction_journal (config) is set, request the non-privileged
reactions intent and subscribe to on_raw_reaction_add/remove, appending one
JSON line per raw transition in the reaction_state core's journal schema
{channel_id, message_id, emoji, user_id, action, seq, ts}.
Raw events fire regardless of message cache, so a reaction on an old/un-cached
card is still captured — the durable-triage use case (greenhouse seed_triage)
the REST poller approximates. Default OFF: no intent change, no extra gateway
traffic, no behavior change for anyone who hasn't set the config key. Writes are
best-effort (a journal error can never crash the gateway event loop); the seq
counter seeds from the existing journal max so a restart never rewinds it.
Covers the journal contract (exact reaction_state schema), custom-emoji name:id form, add→remove ordering, monotonic seq resuming above the existing journal max (restart-safety), no-op when unconfigured, best-effort never-raises on a bad payload, and an end-to-end ingest by the reaction_state core proving byte-compatibility (skips cleanly if the core isn't in this checkout).
🔎 Lint report:
|
| Rule | Count |
|---|---|
unresolved-import |
1 |
invalid-argument-type |
1 |
First entries
tests/gateway/test_discord_reaction_journal.py:197: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/gateway/test_discord_reaction_journal.py:201: [invalid-argument-type] invalid-argument-type: Argument to function `module_from_spec` is incorrect: Expected `ModuleSpec`, found `ModuleSpec | None`
✅ Fixed issues: none
Unchanged: 6399 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
- P1: non-dict JSON line in the journal raised AttributeError out of seed scanning, leaving _reaction_seq unset → every subsequent append retried the same bad line and the journal went permanently dark. Now skip non-dict lines (isinstance guard) and catch AttributeError. RED-proven regression test added. - P2: makedirs ran on every append; now guarded by a once-per-process flag. - P2: sync file I/O ran inside the async on_raw_reaction handlers; now offloaded via run_in_executor (_emit_reaction_journal) so an emoji-storm can't stall the gateway event loop.
…dapter
Ground-truthed the config path: the Discord adapter is env-driven by
convention (_apply_yaml_config translates discord.* YAML keys to DISCORD_*
env vars; it seeds nothing into PlatformConfig.extra). Reading
config.extra.get('reaction_journal') would therefore have been silently dead —
the config key never reaches the adapter. Fix:
- read DISCORD_REACTION_JOURNAL env (fallback to extra for nested
platforms.discord.extra users),
- add the config.yaml discord.reaction_journal -> DISCORD_REACTION_JOURNAL
bridge in _apply_yaml_config (env takes precedence, matching every other key),
- tests for the bridge + precedence; make the cross-repo core-ingest test
path-agnostic (glob for tools/reaction_state.py) since the deployed layout
moved to versioned symlink dirs.
Greptile P-level: _next_reaction_seq is now invoked from run_in_executor (the async-offload fix), so its read-increment-write on self._reaction_seq raced — two concurrent burst events could mint the SAME seq, and the reaction_state core drops a duplicate/stale seq → silent event loss. Guard the read-increment-write with a per-instance threading.Lock (lazily created under a module-level init lock so the lazy-init is itself race-free). Added a 16-thread/50-iter concurrency test asserting all seqs are unique + a clean monotonic run (RED-proven: the lock-free version mints ~750/800 duplicates).
Kyzcreig
added a commit
that referenced
this pull request
Sep 26, 2026
Fork-PR audit verdict DROP (lead t_03e35f0e, card t_d72c6944): the journal is written but nothing in the fleet reads it on a schedule. The live journal (~/.hermes/greenhouse/reactions.jsonl) is at seq 40486 on 2026-09-25, so the feature does fire. Its only reader is the manual `seed_triage.py report --journal` in ANG-Ventures/greenhouse-tools (verdict-age column). That tool is not scheduled, and there is no local checkout of it on the Studio. Reverts 56be472. The conflict in adapter.py was resolved by keeping main's seeded_extra/_skip_env_bridge shape and removing the reaction_journal keys. Verified: tests/gateway/test_discord_connect.py plus test_discord_reactions.py give 19 passed and 1 failed. The failure is test_post_connect_initialization_retries_fingerprint_after_timeout, and it fails the same way on fork/main 382fc62.
ang-fleet-workers Bot
pushed a commit
that referenced
this pull request
Sep 27, 2026
Fork-PR audit verdict DROP (lead t_03e35f0e, card t_d72c6944): the journal is written but nothing in the fleet reads it on a schedule. The live journal (~/.hermes/greenhouse/reactions.jsonl) is at seq 40486 on 2026-09-25, so the feature does fire. Its only reader is the manual `seed_triage.py report --journal` in ANG-Ventures/greenhouse-tools (verdict-age column). That tool is not scheduled, and there is no local checkout of it on the Studio. Reverts 56be472. The conflict in adapter.py was resolved by keeping main's seeded_extra/_skip_env_bridge shape and removing the reaction_journal keys. Verified: tests/gateway/test_discord_connect.py plus test_discord_reactions.py give 19 passed and 1 failed. The failure is test_post_connect_initialization_retries_fingerprint_after_timeout, and it fails the same way on fork/main 382fc62.
This was referenced Sep 27, 2026
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.
What
When
discord.reaction_journal(config.yaml) is set, the Discord adapter requests the non-privilegedreactionsintent and subscribes toon_raw_reaction_add/remove, appending one JSON line per raw transition to that path in thereaction_statecore's journal schema:{channel_id, message_id, emoji, user_id, action, seq, ts}.Why
Raw reaction events fire regardless of message cache, so a reaction on an old / un-cached message is still captured — the durable-triage use case (greenhouse
seed_triage, which currently approximates this with a REST poller). This taps the gateway's existing Discord connection rather than opening a second gateway session on the same bot token (which Discord forbids).Footprint / safety (per AGENTS.md rubric)
HERMES_*env var (the path is non-secret config).seed_triage), so it's not speculative.Tests
tests/gateway/test_discord_reaction_journal.py(7, all green): exact-schema, custom-emojiname:id, add→remove ordering, restart-safe seq, unconfigured no-op, never-raises-on-bad-payload, and an end-to-end ingest by the reaction_state core proving byte-compatibility. Existing adapter suite stays green (27 in test_discord_connect).