fix(cron): stop injecting cron outputs into gateway session history - #2313
Merged
Conversation
Cron deliveries were mirrored into the target gateway session as assistant-role messages, causing consecutive assistant messages that violate message alternation (issue #2221). Instead of fixing the role, remove the mirror injection entirely. Cron outputs already live in their own cron session and don't belong in the interactive conversation history. Delivered messages are now wrapped with a header (task name) and a footer noting the agent cannot see or respond to the message, so users have clear context about what they're reading. Closes #2221
1 task
outsourc-e
pushed a commit
to outsourc-e/hermes-agent
that referenced
this pull request
Mar 26, 2026
…ousResearch#2313) Cron deliveries were mirrored into the target gateway session as assistant-role messages, causing consecutive assistant messages that violate message alternation (issue NousResearch#2221). Instead of fixing the role, remove the mirror injection entirely. Cron outputs already live in their own cron session and don't belong in the interactive conversation history. Delivered messages are now wrapped with a header (task name) and a footer noting the agent cannot see or respond to the message, so users have clear context about what they're reading. Closes NousResearch#2221
angelburgosrosado
pushed a commit
to angelburgosrosado/hermes-agent
that referenced
this pull request
Apr 27, 2026
…ousResearch#2313) Cron deliveries were mirrored into the target gateway session as assistant-role messages, causing consecutive assistant messages that violate message alternation (issue NousResearch#2221). Instead of fixing the role, remove the mirror injection entirely. Cron outputs already live in their own cron session and don't belong in the interactive conversation history. Delivered messages are now wrapped with a header (task name) and a footer noting the agent cannot see or respond to the message, so users have clear context about what they're reading. Closes NousResearch#2221
JinyuID
added a commit
to JinyuID/hermes-agent
that referenced
this pull request
May 11, 2026
…stant Restores the user-visible benefit of the cron→session mirror that NousResearch#2313 removed (PR NousResearch#2313 / issue NousResearch#2221), without re-introducing the consecutive- assistant-message problem that broke alternation. Strategy: when a cron job finishes and delivers to a platform, look up the chat session for that (platform, chat_id, thread_id, user_id), read the last message, and: - if role == 'assistant' → APPEND the cron content to that message's content field (UPDATE, no new row) - otherwise → INSERT a new assistant message (safe — prior turn was user/tool/empty) Each merge stamps an idempotency marker '<!-- cron-merge:<job_id>:<fire_ts> -->' so double-fires/replays don't double-append. Gated by config flag gateway.cron_session_merge.enabled (default OFF). All errors swallowed with logger.warning — never affects cron delivery. Tests: 7 new in tests/gateway/test_cron_session_merge.py covering update-vs-insert, empty session, disabled config, local/all skip, db exception isolation, and idempotency. Existing tests/cron/test_scheduler.py::test_no_mirror_to_session_call still passes because the merge path uses UPDATE/INSERT on hermes_state directly and never calls gateway.mirror.mirror_to_session.
02356abc
pushed a commit
to 02356abc/hermes-agent
that referenced
this pull request
May 14, 2026
…ousResearch#2313) Cron deliveries were mirrored into the target gateway session as assistant-role messages, causing consecutive assistant messages that violate message alternation (issue NousResearch#2221). Instead of fixing the role, remove the mirror injection entirely. Cron outputs already live in their own cron session and don't belong in the interactive conversation history. Delivered messages are now wrapped with a header (task name) and a footer noting the agent cannot see or respond to the message, so users have clear context about what they're reading. Closes NousResearch#2221
This was referenced Jun 1, 2026
gweeteve
pushed a commit
to gweeteve/hermes-agent
that referenced
this pull request
Jun 2, 2026
…ousResearch#2313) Cron deliveries were mirrored into the target gateway session as assistant-role messages, causing consecutive assistant messages that violate message alternation (issue NousResearch#2221). Instead of fixing the role, remove the mirror injection entirely. Cron outputs already live in their own cron session and don't belong in the interactive conversation history. Delivered messages are now wrapped with a header (task name) and a footer noting the agent cannot see or respond to the message, so users have clear context about what they're reading. Closes NousResearch#2221
JinyuID
added a commit
to JinyuID/hermes-agent
that referenced
this pull request
Jun 8, 2026
…stant Restores the user-visible benefit of the cron→session mirror that NousResearch#2313 removed (PR NousResearch#2313 / issue NousResearch#2221), without re-introducing the consecutive- assistant-message problem that broke alternation. Strategy: when a cron job finishes and delivers to a platform, look up the chat session for that (platform, chat_id, thread_id, user_id), read the last message, and: - if role == 'assistant' → APPEND the cron content to that message's content field (UPDATE, no new row) - otherwise → INSERT a new assistant message (safe — prior turn was user/tool/empty) Each merge stamps an idempotency marker '<!-- cron-merge:<job_id>:<fire_ts> -->' so double-fires/replays don't double-append. Gated by config flag gateway.cron_session_merge.enabled (default OFF). All errors swallowed with logger.warning — never affects cron delivery. Tests: 7 new in tests/gateway/test_cron_session_merge.py covering update-vs-insert, empty session, disabled config, local/all skip, db exception isolation, and idempotency. Existing tests/cron/test_scheduler.py::test_no_mirror_to_session_call still passes because the merge path uses UPDATE/INSERT on hermes_state directly and never calls gateway.mirror.mirror_to_session.
victor-kyriazakos
added a commit
to victor-kyriazakos/hermes-agent
that referenced
this pull request
Jun 24, 2026
…on-safe) Addresses review on NousResearch#51077 (kxee). The continuable-cron mirror reused gateway.mirror.mirror_to_session, which writes role=assistant — re- introducing the exact alternation violation NousResearch#2313 (37a9979) deliberately removed: a cron brief landing as assistant after the agent's last turn yields assistant->assistant, which breaks strict- alternation providers (OpenAI/OpenRouter) per issue NousResearch#2221. The mirror/ mirror_source metadata is also dropped at the SQLite boundary, so the [Delivered from cron] label is lost on replay. This is an intentional, opt-in (default OFF) reversal of NousResearch#2313's 'cron output does not belong in interactive history' for the reply-to- cron use case — gated behind cron.mirror_delivery / attach_to_session. Fixes: - mirror_to_session gains a role param (default 'assistant' — interactive send_message mirror unchanged, it IS the agent speaking). Cron paths pass role='user' with a '[Cron delivery: <task>]' prefix so the brief collapses via repair_message_sequence's consecutive-user merge on every provider, and stays distinguishable on replay despite the metadata drop. - thread_seeded: defer seeding + the flag until delivery into the new thread actually succeeds. Previously set pre-delivery, so an open- succeeds / deliver-fails case both stranded a seeded-but-unseen brief AND suppressed the DM-fallback mirror. - seed mirror now passes user_id='system:cron' to resolve the exact thread-keyed session row it just created. - dedupe the duplicate BasePlatformAdapter import in _deliver_result. - trim oversized docstrings to non-obvious WHY (AGENTS.md). - docs: document cron.mirror_delivery / attach_to_session in website/docs/user-guide/features/cron.md. - test: assert the cron mirror writes role='user' with the label prefix. 204 cron+mirror tests pass.
teknium1
pushed a commit
that referenced
this pull request
Jun 25, 2026
…on-safe) Addresses review on #51077 (kxee). The continuable-cron mirror reused gateway.mirror.mirror_to_session, which writes role=assistant — re- introducing the exact alternation violation #2313 (37a9979) deliberately removed: a cron brief landing as assistant after the agent's last turn yields assistant->assistant, which breaks strict- alternation providers (OpenAI/OpenRouter) per issue #2221. The mirror/ mirror_source metadata is also dropped at the SQLite boundary, so the [Delivered from cron] label is lost on replay. This is an intentional, opt-in (default OFF) reversal of #2313's 'cron output does not belong in interactive history' for the reply-to- cron use case — gated behind cron.mirror_delivery / attach_to_session. Fixes: - mirror_to_session gains a role param (default 'assistant' — interactive send_message mirror unchanged, it IS the agent speaking). Cron paths pass role='user' with a '[Cron delivery: <task>]' prefix so the brief collapses via repair_message_sequence's consecutive-user merge on every provider, and stays distinguishable on replay despite the metadata drop. - thread_seeded: defer seeding + the flag until delivery into the new thread actually succeeds. Previously set pre-delivery, so an open- succeeds / deliver-fails case both stranded a seeded-but-unseen brief AND suppressed the DM-fallback mirror. - seed mirror now passes user_id='system:cron' to resolve the exact thread-keyed session row it just created. - dedupe the duplicate BasePlatformAdapter import in _deliver_result. - trim oversized docstrings to non-obvious WHY (AGENTS.md). - docs: document cron.mirror_delivery / attach_to_session in website/docs/user-guide/features/cron.md. - test: assert the cron mirror writes role='user' with the label prefix. 204 cron+mirror tests pass.
pai-scaffolde
pushed a commit
to pai-scaffolde/hermes-agent
that referenced
this pull request
Jun 28, 2026
…on-safe) Addresses review on NousResearch#51077 (kxee). The continuable-cron mirror reused gateway.mirror.mirror_to_session, which writes role=assistant — re- introducing the exact alternation violation NousResearch#2313 (37a9979) deliberately removed: a cron brief landing as assistant after the agent's last turn yields assistant->assistant, which breaks strict- alternation providers (OpenAI/OpenRouter) per issue NousResearch#2221. The mirror/ mirror_source metadata is also dropped at the SQLite boundary, so the [Delivered from cron] label is lost on replay. This is an intentional, opt-in (default OFF) reversal of NousResearch#2313's 'cron output does not belong in interactive history' for the reply-to- cron use case — gated behind cron.mirror_delivery / attach_to_session. Fixes: - mirror_to_session gains a role param (default 'assistant' — interactive send_message mirror unchanged, it IS the agent speaking). Cron paths pass role='user' with a '[Cron delivery: <task>]' prefix so the brief collapses via repair_message_sequence's consecutive-user merge on every provider, and stays distinguishable on replay despite the metadata drop. - thread_seeded: defer seeding + the flag until delivery into the new thread actually succeeds. Previously set pre-delivery, so an open- succeeds / deliver-fails case both stranded a seeded-but-unseen brief AND suppressed the DM-fallback mirror. - seed mirror now passes user_id='system:cron' to resolve the exact thread-keyed session row it just created. - dedupe the duplicate BasePlatformAdapter import in _deliver_result. - trim oversized docstrings to non-obvious WHY (AGENTS.md). - docs: document cron.mirror_delivery / attach_to_session in website/docs/user-guide/features/cron.md. - test: assert the cron mirror writes role='user' with the label prefix. 204 cron+mirror tests pass.
waefrebeorn
pushed a commit
to waefrebeorn/slermes
that referenced
this pull request
Jul 2, 2026
…on-safe) Addresses review on NousResearch#51077 (kxee). The continuable-cron mirror reused gateway.mirror.mirror_to_session, which writes role=assistant — re- introducing the exact alternation violation NousResearch#2313 (37a9979) deliberately removed: a cron brief landing as assistant after the agent's last turn yields assistant->assistant, which breaks strict- alternation providers (OpenAI/OpenRouter) per issue NousResearch#2221. The mirror/ mirror_source metadata is also dropped at the SQLite boundary, so the [Delivered from cron] label is lost on replay. This is an intentional, opt-in (default OFF) reversal of NousResearch#2313's 'cron output does not belong in interactive history' for the reply-to- cron use case — gated behind cron.mirror_delivery / attach_to_session. Fixes: - mirror_to_session gains a role param (default 'assistant' — interactive send_message mirror unchanged, it IS the agent speaking). Cron paths pass role='user' with a '[Cron delivery: <task>]' prefix so the brief collapses via repair_message_sequence's consecutive-user merge on every provider, and stays distinguishable on replay despite the metadata drop. - thread_seeded: defer seeding + the flag until delivery into the new thread actually succeeds. Previously set pre-delivery, so an open- succeeds / deliver-fails case both stranded a seeded-but-unseen brief AND suppressed the DM-fallback mirror. - seed mirror now passes user_id='system:cron' to resolve the exact thread-keyed session row it just created. - dedupe the duplicate BasePlatformAdapter import in _deliver_result. - trim oversized docstrings to non-obvious WHY (AGENTS.md). - docs: document cron.mirror_delivery / attach_to_session in website/docs/user-guide/features/cron.md. - test: assert the cron mirror writes role='user' with the label prefix. 204 cron+mirror tests pass.
habarmc1223-sudo
pushed a commit
to habarmc1223-sudo/hermes-agent-fluxmem
that referenced
this pull request
Jul 8, 2026
…on-safe) Addresses review on NousResearch#51077 (kxee). The continuable-cron mirror reused gateway.mirror.mirror_to_session, which writes role=assistant — re- introducing the exact alternation violation NousResearch#2313 (228472e) deliberately removed: a cron brief landing as assistant after the agent's last turn yields assistant->assistant, which breaks strict- alternation providers (OpenAI/OpenRouter) per issue NousResearch#2221. The mirror/ mirror_source metadata is also dropped at the SQLite boundary, so the [Delivered from cron] label is lost on replay. This is an intentional, opt-in (default OFF) reversal of NousResearch#2313's 'cron output does not belong in interactive history' for the reply-to- cron use case — gated behind cron.mirror_delivery / attach_to_session. Fixes: - mirror_to_session gains a role param (default 'assistant' — interactive send_message mirror unchanged, it IS the agent speaking). Cron paths pass role='user' with a '[Cron delivery: <task>]' prefix so the brief collapses via repair_message_sequence's consecutive-user merge on every provider, and stays distinguishable on replay despite the metadata drop. - thread_seeded: defer seeding + the flag until delivery into the new thread actually succeeds. Previously set pre-delivery, so an open- succeeds / deliver-fails case both stranded a seeded-but-unseen brief AND suppressed the DM-fallback mirror. - seed mirror now passes user_id='system:cron' to resolve the exact thread-keyed session row it just created. - dedupe the duplicate BasePlatformAdapter import in _deliver_result. - trim oversized docstrings to non-obvious WHY (AGENTS.md). - docs: document cron.mirror_delivery / attach_to_session in website/docs/user-guide/features/cron.md. - test: assert the cron mirror writes role='user' with the label prefix. 204 cron+mirror tests pass.
santhreal
pushed a commit
to santhreal/hermes-agent
that referenced
this pull request
Jul 13, 2026
…on-safe) Addresses review on NousResearch#51077 (kxee). The continuable-cron mirror reused gateway.mirror.mirror_to_session, which writes role=assistant — re- introducing the exact alternation violation NousResearch#2313 (228472e) deliberately removed: a cron brief landing as assistant after the agent's last turn yields assistant->assistant, which breaks strict- alternation providers (OpenAI/OpenRouter) per issue NousResearch#2221. The mirror/ mirror_source metadata is also dropped at the SQLite boundary, so the [Delivered from cron] label is lost on replay. This is an intentional, opt-in (default OFF) reversal of NousResearch#2313's 'cron output does not belong in interactive history' for the reply-to- cron use case — gated behind cron.mirror_delivery / attach_to_session. Fixes: - mirror_to_session gains a role param (default 'assistant' — interactive send_message mirror unchanged, it IS the agent speaking). Cron paths pass role='user' with a '[Cron delivery: <task>]' prefix so the brief collapses via repair_message_sequence's consecutive-user merge on every provider, and stays distinguishable on replay despite the metadata drop. - thread_seeded: defer seeding + the flag until delivery into the new thread actually succeeds. Previously set pre-delivery, so an open- succeeds / deliver-fails case both stranded a seeded-but-unseen brief AND suppressed the DM-fallback mirror. - seed mirror now passes user_id='system:cron' to resolve the exact thread-keyed session row it just created. - dedupe the duplicate BasePlatformAdapter import in _deliver_result. - trim oversized docstrings to non-obvious WHY (AGENTS.md). - docs: document cron.mirror_delivery / attach_to_session in website/docs/user-guide/features/cron.md. - test: assert the cron mirror writes role='user' with the label prefix. 204 cron+mirror tests pass.
beardthelion
added a commit
to beardthelion/hermes-agent
that referenced
this pull request
Jul 17, 2026
Cron deliveries don't enter the interactive conversation history (the assistant-role mirror was removed in NousResearch#2313 because it broke message alternation). This adds the pull side of cron session-awareness: the agent can read back what its jobs delivered, on demand. - cronjob(action='output'[, job_id][, limit]) returns recent deliveries, newest first, parsed from ~/.hermes/cron/output/<job_id>/*.md - _extract_delivered_content handles both saved-file shapes: agent-mode ("## Response") and no_agent ("---" then script stdout) - schema: new 'output' action + 'limit' param Pull side only. Push side (auto-injecting a [System note] into the next turn's system prompt) is the next step and is not in this commit. Tests: 6 new in TestReadRecentOutputs; 62 passed. (cherry picked from commit 0bc07530f45195a82636b9243d3cbe64c8635277)
Gravezzz
pushed a commit
to Gravezzz/hermes-agent
that referenced
this pull request
Jul 21, 2026
…on-safe) Addresses review on NousResearch#51077 (kxee). The continuable-cron mirror reused gateway.mirror.mirror_to_session, which writes role=assistant — re- introducing the exact alternation violation NousResearch#2313 (37a9979) deliberately removed: a cron brief landing as assistant after the agent's last turn yields assistant->assistant, which breaks strict- alternation providers (OpenAI/OpenRouter) per issue NousResearch#2221. The mirror/ mirror_source metadata is also dropped at the SQLite boundary, so the [Delivered from cron] label is lost on replay. This is an intentional, opt-in (default OFF) reversal of NousResearch#2313's 'cron output does not belong in interactive history' for the reply-to- cron use case — gated behind cron.mirror_delivery / attach_to_session. Fixes: - mirror_to_session gains a role param (default 'assistant' — interactive send_message mirror unchanged, it IS the agent speaking). Cron paths pass role='user' with a '[Cron delivery: <task>]' prefix so the brief collapses via repair_message_sequence's consecutive-user merge on every provider, and stays distinguishable on replay despite the metadata drop. - thread_seeded: defer seeding + the flag until delivery into the new thread actually succeeds. Previously set pre-delivery, so an open- succeeds / deliver-fails case both stranded a seeded-but-unseen brief AND suppressed the DM-fallback mirror. - seed mirror now passes user_id='system:cron' to resolve the exact thread-keyed session row it just created. - dedupe the duplicate BasePlatformAdapter import in _deliver_result. - trim oversized docstrings to non-obvious WHY (AGENTS.md). - docs: document cron.mirror_delivery / attach_to_session in website/docs/user-guide/features/cron.md. - test: assert the cron mirror writes role='user' with the label prefix. 204 cron+mirror tests pass.
1 task
leewenjie
pushed a commit
to leewenjie/hermes-agent
that referenced
this pull request
Aug 7, 2026
…on-safe) Addresses review on NousResearch#51077 (kxee). The continuable-cron mirror reused gateway.mirror.mirror_to_session, which writes role=assistant — re- introducing the exact alternation violation NousResearch#2313 (37a9979) deliberately removed: a cron brief landing as assistant after the agent's last turn yields assistant->assistant, which breaks strict- alternation providers (OpenAI/OpenRouter) per issue NousResearch#2221. The mirror/ mirror_source metadata is also dropped at the SQLite boundary, so the [Delivered from cron] label is lost on replay. This is an intentional, opt-in (default OFF) reversal of NousResearch#2313's 'cron output does not belong in interactive history' for the reply-to- cron use case — gated behind cron.mirror_delivery / attach_to_session. Fixes: - mirror_to_session gains a role param (default 'assistant' — interactive send_message mirror unchanged, it IS the agent speaking). Cron paths pass role='user' with a '[Cron delivery: <task>]' prefix so the brief collapses via repair_message_sequence's consecutive-user merge on every provider, and stays distinguishable on replay despite the metadata drop. - thread_seeded: defer seeding + the flag until delivery into the new thread actually succeeds. Previously set pre-delivery, so an open- succeeds / deliver-fails case both stranded a seeded-but-unseen brief AND suppressed the DM-fallback mirror. - seed mirror now passes user_id='system:cron' to resolve the exact thread-keyed session row it just created. - dedupe the duplicate BasePlatformAdapter import in _deliver_result. - trim oversized docstrings to non-obvious WHY (AGENTS.md). - docs: document cron.mirror_delivery / attach_to_session in website/docs/user-guide/features/cron.md. - test: assert the cron mirror writes role='user' with the label prefix. 204 cron+mirror tests pass.
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.
Summary
Cron deliveries were mirrored into the target gateway session as
assistant-role messages viamirror_to_session(). When the session's last message was alsoassistant(the agent's prior response), this created consecutive assistant messages — violating message alternation and causing potential API rejections on strict providers (issue #2221).Fix: Remove the mirror injection entirely. Cron outputs already live in their own cron session and don't belong in the interactive conversation history. The
mirror_to_sessioncall is removed from_deliver_result()in the scheduler.Delivered messages are now wrapped with:
Cronjob Response: <task name>with a separatorThis gives users clear context about what they're reading without polluting the conversation history.
The
mirror_to_sessionsystem itself is untouched — it's still used bysend_message_toolfor cross-session message mirroring, which is a different use case.Closes #2221
Supersedes #2264
Changes
cron/scheduler.py— wrap delivery content with header/footer, removemirror_to_sessioncalltests/cron/test_scheduler.py— replace mirror tests with wrapper/formatting testsTest plan