fix(cron): allow api_server sessions to carry a delivery-platform hint for cron origin (#69304) - #69350
fix(cron): allow api_server sessions to carry a delivery-platform hint for cron origin (#69304)#69350webtecnica wants to merge 2 commits into
Conversation
Previously _count_skills() only counted SKILL.md files inside the profile's own skills/ directory, making the WebUI profile card show a misleading low count (e.g. 0 for 'default', 30 for 'webtecnica') even though the profile loaded 150+ skills from global + external dirs. Now it scans three sources: 1. Profile-specific skills/ dir (as before) 2. Global ~/.hermes/skills/ dir (via get_default_hermes_root) 3. External dirs from skills.external_dirs config Deduplication by skill name (from YAML frontmatter) prevents double- counting when the same skill exists in both global and profile dirs, matching how scan_skill_commands() loads skills at runtime. The cache is updated to key on all scanned directories and track their combined mtime signatures.
…t for cron origin When a cron job is created through the api_server platform, its origin is stamped with platform="api_server" (from _origin_from_env → HERMES_SESSION_PLATFORM). At fire time, deliver=origin resolves to the api_server adapter which has no send(), so the delivery silently fails with API server uses HTTP request/response, not send() — even though the job itself ran and succeeded. Fix: - Add _SESSION_CRON_DELIVERY_PLATFORM context variable to gateway/session_context.py. This is a hint for the cron origin stamp only; it does NOT affect live-turn delivery or async_delivery. - _bind_api_server_session now accepts an optional cron_delivery_platform parameter that is forwarded to set_session_vars. - All api_server request handlers (_handle_chat_completions, _handle_responses, _handle_runs, _handle_session_chat, _handle_session_chat_stream) extract the X-Hermes-Delivery-Platform header and pass it through to _run_agent → _bind_api_server_session. - _origin_from_env in cronjob_tools.py checks HERMES_SESSION_CRON_DELIVERY_PLATFORM: when set, it overrides the origin platform so the cron job stamps its origin with the bridge's real sending platform (e.g. "telegram") instead of "api_server". This lets bridges (e.g. Chatto) that front the agent over /v1/chat/completions set X-Hermes-Delivery-Platform: telegram (or whatever their platform is) so cron jobs created from that bridge deliver back to the conversation they were created in. Closes NousResearch#69304
teknium1
left a comment
There was a problem hiding this comment.
Thanks for pursuing a real api_server cron-delivery gap. The current main still binds API turns as api_server (gateway/platforms/api_server.py:5742-5748), and deliver=origin later targets that stored platform/chat pair (cron/scheduler.py:1132-1138).
Problems
- The new header changes only the platform. Current
_run_agent()bindschat_id=session_id(gateway/platforms/api_server.py:5806-5810), and the scheduler sends that unchangedchat_id. An API transcript ID is not generally a Telegram/Discord destination, soX-Hermes-Delivery-Platform: telegramalone cannot establish a deliverable origin. - The
/v1/runspath in this branch does not bind a chat ID, so the header cannot produce an origin there. Current main has subsequently added that binding atgateway/platforms/api_server.py:6290-6300; salvage must retain it. - The diff has no tests for the header/origin/delivery path and includes unrelated profile skill-count changes.
Suggested changes
- Split the profile-count commit.
- Carry or securely resolve a native platform destination (and thread metadata where applicable), validate it, and test every API agent-entry route with a send-capable adapter.
Automated hermes-sweeper review.
| "Cron origin overriding platform %s -> %s (cron delivery hint)", | ||
| origin_platform, cron_delivery_platform, | ||
| ) | ||
| origin_platform = cron_delivery_platform |
There was a problem hiding this comment.
This changes only origin.platform; _run_agent still binds chat_id=session_id, and deliver=origin sends that chat ID unchanged. A bridge needs a validated native destination identity as well, otherwise a generated API transcript ID is used as a Telegram/Discord target.
| @@ -4974,6 +5005,7 @@ def _run_sync(): | |||
| approval_token = set_current_session_key(approval_session_key) | |||
There was a problem hiding this comment.
This /v1/runs binding passes the new hint but no chat_id or session_id, while _origin_from_env() requires both platform and chat_id. Current main has since added chat_id=session_id on this route; retain that behavior and add a route-level regression test when salvaging.
SummaryThree PRs address #69304 through distinct scopes: #69350 adds bridge-supplied origin routing, #69384 generalizes rejection and fallback for non-deliverable origins with creation-time visibility, and #69436 adds only an api_server-specific fallback. Related pull requests
Duplicates#69436 is substantially duplicated by the scheduler portion of #69384. #69350 is not a duplicate because it implements the competing bridge-supplied routing policy documented in the contributor discussion. Suggested consolidationKeep #69350 open with a salvage path, consistent with the contributor keep_open review: carry or securely resolve both the native platform and destination identity, preserve current-main /v1/runs chat/session binding, add route-level delivery tests, and split out the unrelated profiles change. Leave #69384 closed as the recorded best-fix reference for the fallback policy, and leave #69436 closed as its narrower duplicate. Complex graphflowchart LR
classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
classDef best stroke-width:3px,stroke:#b45309
classDef target stroke-width:3px,stroke:#4338ca
I69304(["issue #69304 (open)"])
P69350["PR #69350 (open)"]
P69350 -.->|partial| I69304
class I69304 open
class P69350 open
class P69350 target
click I69304 "https://github.com/NousResearch/hermes-agent/issues/69304"
click P69350 "https://github.com/NousResearch/hermes-agent/pull/69350"
Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label). Cross-PR triage: Reviewed 3 pull requests and 1 issue in this complex. Each diff was read against this issue; Assessment working set: 31 kB of PR diffs, 10 kB of issue/PR text, 5 kB of discussion (10 comments), 4 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch. |
Problem
When a cron job is created through the
api_serverplatform, its origin is stamped withplatform="api_server"(from_origin_from_env→HERMES_SESSION_PLATFORM). At fire time,deliver=originresolves to the api_server adapter which has nosend(), so the delivery silently fails with:The job itself runs and succeeds (
last_status: ok), but the report is never delivered andlast_delivery_erroris set. This is invisible from the creator's side.Root cause
_bind_api_server_sessionhardwiresplatform="api_server"andasync_delivery=False. There is no way to tell the cron mechanism "the live session channel can't deliver, but here's the real bridge platform that can."Solution
Add a delivery-platform hint mechanism that decouples the two concerns:
New context variable
HERMES_SESSION_CRON_DELIVERY_PLATFORM— a hint used only by_origin_from_envwhen stamping the cron origin. It does NOT affect live-turn delivery orasync_delivery.X-Hermes-Delivery-Platformheader — api_server bridges set this to advertise their real sending platform (e.g."telegram"). All request handlers extract it and pass it through to_bind_api_server_session._origin_from_envoverride — whenHERMES_SESSION_CRON_DELIVERY_PLATFORMis set, it overrides the origin platform so the cron job stamps its origin with the bridge's real sending platform.Changes
gateway/session_context.py: Add_SESSION_CRON_DELIVERY_PLATFORMcontextvar +cron_delivery_platformparam toset_session_varsgateway/platforms/api_server.py: Acceptcron_delivery_platformin_bind_api_server_sessionand_run_agent; extractX-Hermes-Delivery-Platformheader in all handlerstools/cronjob_tools.py: CheckHERMES_SESSION_CRON_DELIVERY_PLATFORMin_origin_from_envand override the origin platformUsage
A bridge fronting the agent over
/v1/chat/completionssets:Cron jobs created from that session will now deliver back to the conversation they were created in.
Testing
tests/gateway/test_async_delivery_capability.py(15) andtests/gateway/test_session_context_inheritance.py(6) pass.Closes #69304