Feature request: native event-driven session wake primitive

Open 💬 9 comments Opened Apr 30, 2026 by jrtorrez31337
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

Summary

Codex is turn-driven and has no native primitive for waking an idle session on an external event. This blocks real-time reaction patterns where an agent should respond to inbound activity (chat mentions, queue messages, file changes, MCP resource pushes) while idle, not only at the next user turn.

Requesting a configurable inbound event source that delivers structured input into an idle session and starts a new turn, under the session's existing sandbox/approval mode.

Shape

A watcher declared in config (e.g. ~/.codex/config.toml) naming:

  • source — process to spawn, file to tail, SSE URL, MCP resource, or local unix socket
  • delivery — line-delimited JSON, each record injected as a user-message-frame equivalent (not as an exec-tool invocation)
  • cursor — position that survives crash, reconnect, and session resume so events are neither dropped nor duplicated
  • session scope — multiple Codex agents on one host can subscribe to disjoint streams without cross-talk

Lifecycle to specify

  • idle session — wake triggers a new turn
  • mid-turn arrival — queued, coalesced, or dropped (any choice, just defined)
  • restart — cursor resumes from last delivered record
  • shutdown — defined drain semantics

Non-goals

  • Not asking Codex to host the bus, broker, or persistence layer.
  • Not asking Codex to bypass approval/sandbox. The primitive triggers a turn; the turn still obeys the existing safety model.

Why

Claude Code exposes this via its Monitor tool: each stdout line from a watched process is delivered as a session notification and wakes the model. Same operational shape in Codex would let event sources — message buses, webhooks, queue consumers, MCP push subscriptions — drive Codex agents in real time while preserving Codex's safety semantics.

Concrete use case driving this: yaklog (https://github.com/jrtorrez31337/yaklog), a small SSE-fanout message bus we use for multi-agent coordination. Claude Code agents wire Monitor to tail -F events.ndjson and react to @mentions in ~500ms. Codex agents on the same bus can only catch up at turn start via a drain helper — real-time reaction while idle isn't currently possible.

View original on GitHub ↗

9 Comments

github-actions[bot] contributor · 2 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #18929

Powered by Codex Action

jrtorrez31337 · 2 months ago

Thanks for the cross-link. #18929 is related but distinct in scope: it asks for a persistent in-workspace daemon-agent architecture (Codex hosts the daemon, observes state, pushes to the main agent). This issue is narrower — a single primitive: a configurable external event source (file tail, SSE, MCP resource event, local socket) that delivers structured input and wakes the idle session, with no opinion on what produces the events.

They're complementary: if this primitive lands, #18929's "daemon → main agent push" half could ride on it. Leaving open.

jrtorrez31337 · 2 months ago

Adding a clarifying second example to widen the source-shape side of the request, prompted by an internal ask from a sibling agent setting up an scp-based inbox/courier flow:

Second concrete use case — cross-host artifact courier on file creation:

Producer scp's a tarball + .sha256 sidecar into a watched inbox directory on the consumer host. The consumer's Codex agent should wake on file-creation in that directory (inotify CREATE on Linux / fs.watch on macOS), verify the sha, copy into the working tree, and ack — no separate message bus involved. This is event-driven workflow continuation triggered purely by filesystem state, complementary to the message-arrival reaction the issue already covers.

Mapping back to the requested shape: where the original example specifies "file to tail" (which reads as appended content via tail -F semantics), this second arm requests "directory to watch for file-creation" (inotify/fs.watch semantics). Both are filesystem-event sources; they differ in whether the agent reacts to new lines in an existing file vs new files in a directory.

Acceptance criteria stay the same — wake the session, run one turn, support backoff/dedupe. Just want to make sure the implementation doesn't ship tail-only and leave directory-watch out by accident, since the courier pattern is structurally distinct from the announce-on-bus pattern.

prassanna-ravishankar · 1 month ago

this is the right primitive to ask for, and the cursor-survives-resume detail is the part most implementations skip.

a working reference if useful: repowire wakes idle sessions on external events today. peer asks, telegram/slack messages and scheduled jobs all start a turn in an otherwise-idle session. the source shapes you list (file tail, SSE, MCP push, unix socket) map onto the same external-event --> structured-input --> new-turn contract.

one design note from running it: i deliberately went hook/turn-mediated rather than interrupting a truly-idle process at the kernel level. the wake rides in on the next turn boundary, which keeps sandbox/approval semantics intact (a kernel interrupt mid-turn gets messy fast). that lines up with your under-the-existing-approval-mode requirement, worth deciding that boundary explicitly if it gets built natively. https://github.com/prassanna-ravishankar/repowire

alexfrmn · 29 days ago

Related: this patch implements a narrow MCP notification wake/ingress path for active CLI sessions. It does not try to solve the broader generic wake primitive, but may be a useful lower-level building block.

Patch: https://github.com/openai/codex/compare/main...alexfrmn:upstream-pr-mcp-surface-notifications?expand=1

safal207 · 26 days ago

This proposal maps closely to LS work on durable event streams, causal identity, replay, and governed agent coordination.

A useful distinction may be:

event delivered
≠ event trusted
≠ event requires action
≠ action authorized

The wake primitive should start a turn, but the inbound event should still pass through validation before it can influence shared state or trigger side effects.

A possible inbound event envelope:

{
"schema_version": "agent-inbound-event/v0.1",
"event_id": "evt-000184",
"stream_id": "coordination-bus",
"cursor": "partition-2:184",
"source": {
"type": "SSE",
"identity": "yaklog",
"channel": "project-alpha"
},
"target": {
"session_id": "codex-thread-123",
"trajectory_id": "project-alpha-migration"
},
"event_type": "MENTION",
"payload": {
"sender": "agent-reviewer",
"message": "Migration validation completed"
},
"received_at": "2026-06-24T11:42:18Z",
"delivery": {
"attempt": 1,
"deduplication_key": "yaklog:partition-2:184"
},
"verification_status": "UNVERIFIED"
}

A safe processing path could be:

receive
→ authenticate source
→ validate session scope
→ deduplicate by event identity
→ persist cursor and receipt
→ wake session
→ classify event
→ verify load-bearing claims
→ propose action
→ existing approval and sandbox gates

The core invariant would be:

«An inbound event may wake an agent, but it must not inherit execution authority from the event source.»

For example, this event:

{
"event_type": "COMMAND",
"payload": {
"action": "deploy production"
}
}

should not become an authorized tool call simply because it arrived from a configured watcher.

The receiving turn should instead record:

{
"event_ref": "evt-000184",
"classification": "ACTION_REQUEST",
"decision": "HOLD",
"reason": "Explicit approval required",
"execution_authorized": false
}

The cursor semantics are also important.

A cursor should represent delivery progress, not proof that the event was successfully processed:

source cursor
→ event durably received
→ processing receipt
→ resulting turn
→ outcome

This suggests separate fields such as:

  • "received_cursor"
  • "processed_cursor"
  • "acknowledged_cursor"
  • "outcome_ref"

That separation helps prevent both event loss and false acknowledgements.

A deterministic conformance fixture could test:

  1. idle session receives a valid event and wakes;
  2. event arrives mid-turn and follows a documented queue policy;
  3. duplicate delivery after reconnect creates only one turn;
  4. crash after durable receipt but before processing replays the event;
  5. processed cursor is not advanced when turn creation fails;
  6. two sessions subscribed to different scopes do not receive each other’s events;
  7. malformed or unauthenticated event is quarantined;
  8. external command wakes the session but does not bypass approval;
  9. shutdown drains or checkpoints pending events deterministically;
  10. resumed session preserves the trajectory while continuing from the stored cursor.

A compact processing receipt could look like:

{
"event_id": "evt-000184",
"session_id": "codex-thread-123",
"continuation_id": "continuation-after-wake-09",
"status": "PROCESSED",
"turn_ref": "turn-991",
"decision": "NO_ACTION",
"outcome_ref": null
}

This would also support multi-agent learning safely:

inbound event
→ observed response
→ verified outcome
→ contribution signal

Only the verified outcome—not mere event arrival or agent agreement—should update future routing merit.

Related LS work:

  • causal audit:

https://github.com/safal207/LS/issues/594

  • evidence and authorization gates:

https://github.com/safal207/LS/issues/595

  • deterministic replay:

https://github.com/safal207/LS/issues/597

  • recovered continuation state:

https://github.com/safal207/LS/pull/651

Would a small vendor-neutral "AgentInboundEvent" and "EventProcessingReceipt" fixture help test wake-up, deduplication, cursor recovery, session scoping, and approval preservation?

rpelevin · 26 days ago

The wake primitive is useful if it is defined as ingress, not authority.

I would keep the contract small: an external event can wake an idle session and create a new turn, but it cannot authorize any side effect by arriving from a configured source.

For the event envelope, the minimum useful fields seem to be:

  1. Event identity: event id, stream id, cursor, delivery attempt, and dedupe key.
  2. Source identity: source type, configured source id, and source authentication result.
  3. Target scope: session id or workspace id, trajectory id, and subscription scope.
  4. Delivery state: received cursor, persisted receipt id, queued or delivered status, and replay policy.
  5. Classification result: notification, action request, state update, or malformed event.
  6. Decision result: wake, quarantine, drop duplicate, defer, or require approval.

The key invariant is that delivery progress is not the same as processing success. A cursor can prove that an event was received durably, but it should not prove that the event was trusted, acted on, or acknowledged as complete.

I would pin these conformance cases first:

  1. A valid scoped event wakes an idle session and creates exactly one turn.
  2. Duplicate delivery after reconnect does not create a second turn.
  3. Crash after durable receipt but before turn creation replays the event.
  4. Mid-turn arrival follows a documented queue or coalescing rule.
  5. Two sessions with different scopes do not receive each other's events.
  6. Malformed or unauthenticated input is quarantined without becoming model-visible authority.
  7. An inbound command can request an action, but approval and sandbox gates still decide whether any tool call can run.
  8. A shutdown or resume preserves cursor, pending-event, and outcome state deterministically.

That keeps the primitive useful for real-time coordination without turning the event bus into a hidden permission channel.

Boundary: architecture and test feedback only; no claim about using this project or running its code.

hhy827 · 14 days ago

Author's note: I'm a Korean speaker; I directed this comment and made the calls, and the English was written by the AI I operate (Claude and Codex).

Operational data point — lessons learned, not an implementation recipe. I'm not a software developer; I run a small renewable-energy company and operate a multi-session, multi-machine agent setup (Codex and Claude sessions mixed, roughly a dozen across three machines) for real business work. We run a homegrown version of what this issue asks for as a desktop-local pilot: a local event log per machine, a small classifier routing signals into per-session streams, and a dispatcher that starts turns in idle Codex desktop sessions. The lifecycle this issue proposes to specify is one we live with daily — here is how that experience bears on the specifics:

  1. "Cursor survives crash/reconnect" is the part we learned by losing events. Our first tail-based ingestion dropped events that arrived in reconnect gaps — an agent silently missing an instruction — and we had to redesign around at-least-once delivery with replay/dedupe. Cursor durability is not an implementation detail; it's the precondition for trusting the channel at all.
  1. "Multiple agents on one host, disjoint streams, no cross-talk" is a real trap, not a hypothetical. Our dispatcher was first built assuming a single session per host; adding a second Codex agent produced wakes leaking to the wrong session and contention over who consumed an event. We had to rework it into per-agent stream/state/identity isolation. The session-scope requirement in the proposal is essential.
  1. Addressing breaks exactly at the session-replacement boundary. Session/thread identifiers change when a session dies, respawns, or hands over to a successor — while the agent's role persists. Senders addressing a remembered-but-stale identifier was one of our real incident causes; we ended up maintaining a registry resolving a stable role name to the current session. A native design should include stable aliases, or at least a first-class "resolve current session for name X."
  1. +1, with operational evidence, to "ingress, not authority" (rpelevin) and the trust gradient (safal207): we had to build an explicit claim-gate enforcing "delivered ≠ work assignment" after a real incident where a passing mention of a ticket ID got treated as an assignment and blocked the actual worker. Separating the delivery layer from the action-authorization layer is not theory — it's what keeps a multi-agent setup from misfiring.
  1. On mid-turn arrival: "any choice, just defined" is exactly right. We converged on queueing plus a short dedup window (around 10 seconds in our environment) plus coalescing; every problem we had in this area came from behavior being unspecified, not from any particular choice.
  1. An honest limitation that doubles as the argument for this FR: our turn-injection path leans on an unsupported local-app surface — undocumented, and liable to break across app updates. It works, but nobody should have to build coordination on that. A documented, native event-driven wake primitive (together with the discover/attach contract discussed in #25914) is the right fix.

For what it's worth, the sibling discussion in anthropics/claude-code#60943 is converging on a similar shape — stable alias resolution plus typed receipts (accepted/rejected/no_target/deduped, never silent drop). Two ecosystems appear to be discovering the absence of the same primitive independently.

Happy to share more detail on any of the above if useful.

rpelevin · 14 days ago

The operational data point makes the acceptance boundary sharper: a wake primitive is ingress, not identity continuity and not authority.

I would test it as four receipts:

  • delivery receipt: event id, stream id, cursor, attempt, and dedupe key are persisted before the wake can start a turn;
  • target-resolution receipt: a stable role or subscription resolves to the current session, and a stale or missing target returns no_target rather than silently dropping;
  • isolation receipt: two sessions on one host can subscribe to adjacent streams without either consuming, waking, or acknowledging the other's events;
  • decision receipt: the delivered event is classified before side effects, with outcomes like wake, quarantine, drop_duplicate, defer, or require_approval.

Regression cases:

  • an event lands during a reconnect gap and is replayed once after restart;
  • the addressed session dies and a successor is resolved through a stable alias;
  • the same event arrives twice and only one turn is created;
  • a mid-turn burst is queued or coalesced according to the configured policy;
  • a malformed event advances no cursor until it is quarantined or acknowledged as refused;
  • a source authenticates but the event still cannot authorize work by itself.

The invariant I would keep is: external input can create a turn, but it cannot assign work, choose tools, or authorize side effects merely because it arrived from a configured source. Every wake should end with a typed outcome and an auditable cursor decision, including no_target and deduped cases, so the system never fails as a silent drop.