Goal
Add a slash command that configures an internal timer to periodically inject a message into the current Codex session as if the user typed it.
Example:
Meaning: every 10 seconds, enqueue a synthetic user message with content hello.
Command Surface
Start / Replace
/cron <interval> "<message>"
<interval>: 500ms, 10s, 2m, 1h (duration parser)
<message>: required, quoted string (support escaped quotes)
Behavior:
- Starts a cron job if none exists
- If one exists, replaces it atomically (no double timers)
Stop
Stops the cron job and clears pending cron messages.
Status
Prints current cron config:
- enabled/disabled
- interval
- message
- last fired timestamp
- pending queue length (if any)
Optional Extensions (not for PoC)
/cron pause
/cron resume
/cron once "<message>"
Semantics
Injection Model
Cron messages are injected as InputEvents into the session input queue:
InputEvent {
source: CRON,
content: <message>,
ts: ...
}
They are processed by the same pipeline as normal user input.
"Interrupt" Behavior
PoC rule (recommended):
- Do not preempt an in-flight model turn
- If the agent is currently running, cron events queue
To avoid runaway backlog:
- Keep max 1 pending cron event (coalesce)
- If a cron tick fires while one is already pending, drop the new tick
This gives "wake up" behavior without turning the session into a spam cannon.
Display
When a cron event fires, show a small UI line before the injected turn runs:
⏱ cron fired (10s): hello
so you can tell it was not user-typed.
Parser Requirements
Duration Parsing
Accept:
ms, s, m, h
- integer only for v1 (e.g.
10s, 2m)
- reject < 200ms (guardrail)
- reject > 24h (guardrail)
Message Parsing
- require quotes for now:
"..." or '...\
- allow escaping (
\", \\, \n optional)
- If user omits quotes: error with usage help
State & Lifecycle
State (in-memory, session-scoped)
CronConfig {
enabled: bool,
interval: Duration,
message: String,
last_fired: Option<Instant>,
dropped_ticks: u64,
}
Lifetime
- Cron job lives only for the current session
- On session exit, cron task stops
- (Persistence can be added later, not for PoC)
Concurrency Model
Single "turn lock"
Ensure only one agent run at a time. Cron just enqueues messages.
Internal Queue
Use an MPSC channel that merges:
Cron Task
A background task/thread:
- sleeps interval
- tries to enqueue cron event
- if pending already exists → coalesce/drop
Cancellation
/cron off cancels the background task cleanly:
- set atomic
enabled=false
- wake task via cancellation token or channel close
- join task if applicable
Error Cases
/cron 0s "hi" → reject (interval too small)
/cron 10s → reject (missing message)
/cron foo "hi" → reject (invalid duration)
/cron off when not running → print "cron disabled"
Minimal Test Plan
Unit Tests
- duration parser
- quoted string parser
/cron command parsing cases
Integration Test
- start session with cron 1s "hello"
- run for ~3s
- assert at least 2 cron events reached the input queue
- assert coalescing works when agent is busy
UX Niceties
When /cron is set, print:
cron enabled: every 10s → "hello" (use /cron off to stop)
When it fires, prefix the injected message in the transcript as:
so it is visually distinct.
Implementation Note
Keep it as a slash command handled at the UI layer before the message hits the model. That way:
- you do not waste tokens
- you can manage timers without involving the LLM
Key Files (from codebase research)
| Purpose |
File |
| Slash commands |
codex-rs/tui2/src/slash_command.rs |
| Op/Event types |
codex-rs/protocol/src/protocol.rs |
| Agent spawning |
codex-rs/tui/src/chatwidget/agent.rs |
| Rate limit poller (reference pattern) |
codex-rs/tui/src/chatwidget.rs:~2950 |
| Frame scheduler (reference pattern) |
codex-rs/tui/src/tui/frame_requester.rs |
Goal
Add a slash command that configures an internal timer to periodically inject a message into the current Codex session as if the user typed it.
Example:
Meaning: every 10 seconds, enqueue a synthetic user message with content
hello.Command Surface
Start / Replace
<interval>:500ms,10s,2m,1h(duration parser)<message>: required, quoted string (support escaped quotes)Behavior:
Stop
Stops the cron job and clears pending cron messages.
Status
Prints current cron config:
Optional Extensions (not for PoC)
Semantics
Injection Model
Cron messages are injected as InputEvents into the session input queue:
They are processed by the same pipeline as normal user input.
"Interrupt" Behavior
PoC rule (recommended):
To avoid runaway backlog:
This gives "wake up" behavior without turning the session into a spam cannon.
Display
When a cron event fires, show a small UI line before the injected turn runs:
so you can tell it was not user-typed.
Parser Requirements
Duration Parsing
Accept:
ms,s,m,h10s,2m)Message Parsing
"..."or'...\\",\\,\noptional)State & Lifecycle
State (in-memory, session-scoped)
Lifetime
Concurrency Model
Single "turn lock"
Ensure only one agent run at a time. Cron just enqueues messages.
Internal Queue
Use an MPSC channel that merges:
Cron Task
A background task/thread:
Cancellation
/cron offcancels the background task cleanly:enabled=falseError Cases
/cron 0s "hi"→ reject (interval too small)/cron 10s→ reject (missing message)/cron foo "hi"→ reject (invalid duration)/cron offwhen not running → print "cron disabled"Minimal Test Plan
Unit Tests
/croncommand parsing casesIntegration Test
UX Niceties
When
/cronis set, print:When it fires, prefix the injected message in the transcript as:
so it is visually distinct.
Implementation Note
Keep it as a slash command handled at the UI layer before the message hits the model. That way:
Key Files (from codebase research)
codex-rs/tui2/src/slash_command.rscodex-rs/protocol/src/protocol.rscodex-rs/tui/src/chatwidget/agent.rscodex-rs/tui/src/chatwidget.rs:~2950codex-rs/tui/src/tui/frame_requester.rs