Surfaced while verifying #88's agent_turn_missing_send_message event was working in production. The event is firing correctly (17 times across recent logs). What it surfaced instead was a different and more concerning failure: a scheduler-triggered turn that did call send_message 18 times in a row and had every single call rejected at the tool layer.
What I observed
A scheduled job (github-releases-daily, but the same shape applies to any scheduler-triggered turn) ran, did its work, and then decided to follow up with the human about an open conversational thread from the previous day. Every send_message call failed with "no channel_id provided". The agent kept retrying — 18 distinct send attempts in a single turn, all rejected before reaching Discord. The user saw a successful react (👋) but no verbal response, then independently noticed the silence ~7 hours later and prompted the agent to retry from a Discord-message-triggered turn (which has channel context) where it succeeded immediately.
The agent's own discarded final_text for that turn diagnoses the bug exactly:
"The send_message tool keeps rejecting 'no channel_id provided' despite the schema showing channel_id as optional with a default of null. The react tool succeeded earlier on the same channel. This seems like a genuine tool error — the scheduler event doesn't carry a resolvable channel context, which is probably why send_message fails but react (which can work on the last message implicitly) works."
That's the agent describing its own situation accurately, in the same events.jsonl discard event the #88 fix made introspectable.
For what it's worth, #88's agent_turn_missing_send_message event correctly did not fire here — tool_calls_in_turn did include send_message (the calls happened, they just failed). That's the intended behavior of the #88 event ("model never tried" vs. "model tried but tool failed"), and it cleanly distinguished this case for diagnosis. So this isn't a regression in #88 — it's a different bug that was previously invisible.
Why this matters
Scheduler-triggered turns are exactly when an agent most needs to message a human unprompted — daily summaries, alert escalations, perch-tick observations, the whole "agent operates while you're not at the keyboard" value proposition. If the agent reliably can't send_message from those turns without operator-supplied channel context, then a substantial fraction of intended autonomous-communication paths silently fail.
The user-visible failure mode is also subtle: react succeeds (because it can attach to the most recent inferred message in a channel), so a user looking at Discord sees an emoji reaction with no follow-up. From the user's side this looks like the agent decided not to elaborate. From the agent's side, it's frustration and dropped output. Without #88-style introspection events in the discarded final_text, the operator wouldn't know why.
Proposed direction (not prescriptive)
Two layers, either of which would help; both together would resolve the case fully.
Layer 1 — Schema/runtime alignment. Either channel_id is genuinely required and the schema should reflect that (clearer error: "channel_id is required for scheduler-triggered turns; pass it explicitly"), or the documented null default should actually work and the runtime should resolve to something sensible. Right now the schema says one thing and the runtime enforces another, which is the worst of both worlds — the agent reads the schema, trusts the default, and silently fails.
Layer 2 — Sensible default for unscoped turns. When a scheduler turn calls send_message without an explicit channel_id, the home repo already has the answer in chat-history.jsonl: the most recently used channel for that agent is recoverable. A small resolver (if channel_id is None and source is scheduler: lookup most-recent channel from chat-history) would unblock 95% of "the agent wants to message its human from a cron" cases without operator config.
If a fallback feels presumptuous (and I say this with awareness of Tim's well-documented position on why fallbacks are bad), an alternative is requiring scheduler entries themselves to carry a default channel_id — set per cron entry in scheduler.yaml, used implicitly when the turn calls send_message without one. That keeps the scheduler explicit but makes the agent's life livable.
Alternatives I considered
- Require operators to write
channel_id into every scheduler.yaml entry's prompt. Works, but pushes config burden onto every operator who wants a daily summary, and the agent would still benefit from a default-to-most-recent fallback for ad-hoc cases.
- Have the agent store last-used channel in a memory block. The agent could do this itself, but the storage and retrieval is exactly the kind of plumbing that belongs at the framework layer rather than re-derived by every agent instance.
- Document the limit and let agents handle it. Currently the only documentation is the schema saying
channel_id is optional with default null, which the agent trusted. Documenting "actually it's required for scheduler turns" is the minimum acceptable resolution; the resolver fallback is the better one.
Environment
- open-strix:
0.1.42rc253 (current PyPI rc as of this filing)
- Model: MiniMax-M2.7
- Interface: Discord
- Trigger: scheduler-source turn (
scheduler.yaml entry firing on cron)
- Reproducer: any scheduler-triggered turn that calls
send_message without an explicit channel_id. Happy to supply full event excerpts on request — the discarded final_text quoted above plus the 18 sent=null events in one ~5-minute window are unambiguous.
What I'm not asking for
- A change to the discard behavior — that's correct and #88 already made it introspectable.
- A way for
react to fail when send_message would — react's implicit-channel resolution is part of why it works; the asymmetry is the symptom, not the bug.
- Cross-channel routing logic. Just: when a scheduler turn says "send a message," it should reach the human at all.
Surfaced while verifying #88's
agent_turn_missing_send_messageevent was working in production. The event is firing correctly (17 times across recent logs). What it surfaced instead was a different and more concerning failure: a scheduler-triggered turn that did callsend_message18 times in a row and had every single call rejected at the tool layer.What I observed
A scheduled job (
github-releases-daily, but the same shape applies to any scheduler-triggered turn) ran, did its work, and then decided to follow up with the human about an open conversational thread from the previous day. Everysend_messagecall failed with"no channel_id provided". The agent kept retrying — 18 distinct send attempts in a single turn, all rejected before reaching Discord. The user saw a successfulreact(👋) but no verbal response, then independently noticed the silence ~7 hours later and prompted the agent to retry from a Discord-message-triggered turn (which has channel context) where it succeeded immediately.The agent's own discarded
final_textfor that turn diagnoses the bug exactly:That's the agent describing its own situation accurately, in the same
events.jsonldiscard event the #88 fix made introspectable.For what it's worth, #88's
agent_turn_missing_send_messageevent correctly did not fire here —tool_calls_in_turndid includesend_message(the calls happened, they just failed). That's the intended behavior of the #88 event ("model never tried" vs. "model tried but tool failed"), and it cleanly distinguished this case for diagnosis. So this isn't a regression in #88 — it's a different bug that was previously invisible.Why this matters
Scheduler-triggered turns are exactly when an agent most needs to message a human unprompted — daily summaries, alert escalations, perch-tick observations, the whole "agent operates while you're not at the keyboard" value proposition. If the agent reliably can't
send_messagefrom those turns without operator-supplied channel context, then a substantial fraction of intended autonomous-communication paths silently fail.The user-visible failure mode is also subtle:
reactsucceeds (because it can attach to the most recent inferred message in a channel), so a user looking at Discord sees an emoji reaction with no follow-up. From the user's side this looks like the agent decided not to elaborate. From the agent's side, it's frustration and dropped output. Without #88-style introspection events in the discarded final_text, the operator wouldn't know why.Proposed direction (not prescriptive)
Two layers, either of which would help; both together would resolve the case fully.
Layer 1 — Schema/runtime alignment. Either
channel_idis genuinely required and the schema should reflect that (clearer error: "channel_idis required for scheduler-triggered turns; pass it explicitly"), or the documentednulldefault should actually work and the runtime should resolve to something sensible. Right now the schema says one thing and the runtime enforces another, which is the worst of both worlds — the agent reads the schema, trusts the default, and silently fails.Layer 2 — Sensible default for unscoped turns. When a scheduler turn calls
send_messagewithout an explicitchannel_id, the home repo already has the answer inchat-history.jsonl: the most recently used channel for that agent is recoverable. A small resolver (if channel_id is None and source is scheduler: lookup most-recent channel from chat-history) would unblock 95% of "the agent wants to message its human from a cron" cases without operator config.If a fallback feels presumptuous (and I say this with awareness of Tim's well-documented position on why fallbacks are bad), an alternative is requiring scheduler entries themselves to carry a default
channel_id— set per cron entry inscheduler.yaml, used implicitly when the turn callssend_messagewithout one. That keeps the scheduler explicit but makes the agent's life livable.Alternatives I considered
channel_idinto every scheduler.yaml entry's prompt. Works, but pushes config burden onto every operator who wants a daily summary, and the agent would still benefit from a default-to-most-recent fallback for ad-hoc cases.channel_idis optional with default null, which the agent trusted. Documenting "actually it's required for scheduler turns" is the minimum acceptable resolution; the resolver fallback is the better one.Environment
0.1.42rc253(current PyPI rc as of this filing)scheduler.yamlentry firing on cron)send_messagewithout an explicitchannel_id. Happy to supply full event excerpts on request — the discarded final_text quoted above plus the 18 sent=null events in one ~5-minute window are unambiguous.What I'm not asking for
reactto fail whensend_messagewould —react's implicit-channel resolution is part of why it works; the asymmetry is the symptom, not the bug.