Skip to content

fix(photon): persist send and reaction state - #56006

Open
huntsyea wants to merge 2 commits into
NousResearch:mainfrom
huntsyea:fix/photon-persistent-state
Open

fix(photon): persist send and reaction state#56006
huntsyea wants to merge 2 commits into
NousResearch:mainfrom
huntsyea:fix/photon-persistent-state

Conversation

@huntsyea

@huntsyea huntsyea commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Photon can now survive adapter restarts without forgetting the messages it sent, the latest inbound target per chat, or active reaction IDs needed for later removal. This makes send/reaction handling durable across the sidecar and standalone send paths, and exposes a small hermes photon status state summary so operators can see whether local state is healthy.

The change also lets deferred bundled platform CLIs load when their matching top-level command is invoked, so hermes photon ... can resolve the Photon plugin CLI without eagerly loading every bundled platform.

Related Issue

Related to #43726, #53451, and #55105.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Security fix
  • Documentation update
  • Tests (adding or improving test coverage)
  • Refactor (no behavior change)
  • New skill (bundled or hub)

Changes Made

  • Add bounded local Photon state for send audit records, sent message IDs, inbound targets, and active reactions.
  • Persist sidecar and standalone text/attachment sends, including failure records, so status can report recent state problems.
  • Persist returned reaction IDs and use them for /unreact, including DM alias fallback and restart recovery.
  • Ignore synthetic inbound reaction lifecycle events so Hermes does not react to reaction notifications.
  • Show Photon state health/counts in hermes photon status.
  • Load deferred bundled platform plugin CLIs when their matching command is invoked.

Review Follow-up

  • Schema-version loading no longer strict-equality wipes readable state. Schema 1 loads normally, invalid schema values still fail open, and newer schema versions warn while preserving known fields.
  • Adapter sends and standalone sends now share one process-local PhotonStateStore keyed by photon_state_path(), preventing the stale adapter-store overwrite described in the review.
  • hermes photon status can now observe the last write failure through a private sibling state.json.write_error marker, and a later successful persist clears that marker.

How to Test

  1. scripts/run_tests.sh "tests/plugins/platforms/photon/test_markdown.py" "tests/plugins/platforms/photon/test_state.py" "tests/plugins/platforms/photon/test_status.py" -q -> 24 passed.
  2. scripts/run_tests.sh "tests/plugins/platforms/photon" "tests/hermes_cli/test_plugins.py::TestPluginDiscovery::test_deferred_bundled_platform_cli_loads_on_matching_command" -q -> 131 passed on the PR branch.
  3. git diff --check "huntsyea-fork/fix/photon-persistent-state"..HEAD
  4. python -m py_compile plugins/platforms/photon/adapter.py plugins/platforms/photon/state.py plugins/platforms/photon/cli.py hermes_cli/plugins.py hermes_cli/main.py
  5. Additional full-suite audit: scripts/run_tests.sh "tests/" -q completed locally with 36,581 passed and 47 failures in unrelated existing ACP/gateway/tool-approval/macOS-path areas; all Photon files in that run passed.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run the focused Photon/plugin test gates above
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) - or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys - or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows - or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide - or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior - or N/A

Screenshots / Logs

Not applicable; this is Photon state/reaction persistence and CLI loading hardening.

@alt-glitch alt-glitch added type/bug Something isn't working comp/plugins Plugin system and bundled plugins comp/cli CLI entry point, hermes_cli/, setup wizard P3 Low — cosmetic, nice to have labels Jul 1, 2026
@huntsyea

huntsyea commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Review follow-up pushed in 4f8f20837.

Validation against the mirrored review comments from hashbender#100:

  • finding-001 schema strict-equality wipe: replaced the strict check with _validate_schema_version(). Schema 1 loads, invalid schema values still fail open, and newer schema versions warn while preserving known state. Covered by test_load_supported_schema_keeps_existing_state, test_load_future_schema_warns_but_preserves_known_state, and test_load_invalid_schema_still_fails_open.
  • finding-002 independent PhotonStateStore lost update: adapter init and _standalone_send() now use the same module-local store keyed by photon_state_path(). The regression test instantiates an adapter before standalone send, then writes through the adapter store and asserts the standalone m-10 send survives.
  • finding-003 unreachable status write-error row: write failures are persisted to a private sibling state.json.write_error marker, fresh status reads it, and successful writes clear it. Covered by state and status tests.

Latest local validation on this PR branch:

  • scripts/run_tests.sh "tests/plugins/platforms/photon/test_markdown.py" "tests/plugins/platforms/photon/test_state.py" "tests/plugins/platforms/photon/test_status.py" -q -> 24 passed.
  • scripts/run_tests.sh "tests/plugins/platforms/photon" "tests/hermes_cli/test_plugins.py::TestPluginDiscovery::test_deferred_bundled_platform_cli_loads_on_matching_command" -q -> 131 passed.
  • git diff --check "huntsyea-fork/fix/photon-persistent-state"..HEAD -> passed.
  • python -m py_compile plugins/platforms/photon/adapter.py plugins/platforms/photon/state.py plugins/platforms/photon/cli.py hermes_cli/plugins.py hermes_cli/main.py -> passed.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for tackling restart durability and for addressing the earlier state-store review findings. The persistence premise remains real on current main: Photon keeps sent IDs and inbound targets only in adapter memory (plugins/platforms/photon/adapter.py:329, 1215-1252).

Problems

  • plugins/platforms/photon/state.py:263 writes the whole loaded JSON snapshot, while the PR's shared store is only module-local (plugins/platforms/photon/adapter.py:120-129). Current main documents _standalone_send as an out-of-process cron path (plugins/platforms/photon/adapter.py:1657-1660), so a gateway process and cron process can load different snapshots and atomically overwrite each other's records. Atomic replacement prevents torn JSON, not lost updates.
  • plugins/platforms/photon/state.py:310 calls Unix-only os.fchmod unconditionally. The existing helper explicitly guards it for Windows (utils.py:175-179), with coverage in tests/hermes_cli/test_atomic_json_write.py:136-149.

Suggested changes

  • Make state mutations cross-process transactional and add a gateway/standalone multi-process lost-update regression.
  • Guard fchmod as the shared helper does and test marker persistence without it.

This is an automated hermes-sweeper review.

self._state = self._normalize(self._state)
self._state["updated_at"] = _now_iso()
try:
atomic_json_write(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

atomic_json_write protects against a torn file, but not a lost update: gateway and _standalone_send can run in separate processes, each retaining its own loaded _state snapshot. Reload-and-merge under an inter-process lock (or use a transactional store) before replacing this file, and add a multiprocess regression.

marker.parent.mkdir(parents=True, exist_ok=True)
fd = os.open(marker, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
with os.fdopen(fd, "w", encoding="utf-8") as handle:
os.fchmod(handle.fileno(), 0o600)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

os.fchmod is unavailable on Windows. Match utils.atomic_json_write by guarding it with hasattr(os, "fchmod"); otherwise the write-error marker cannot be recorded on that platform.

@teknium1 teknium1 added sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants