Skip to content

feat: /cron — In-session periodic message injection #8707

@tnn1t1s

Description

@tnn1t1s

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:

/cron 10s "hello"

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

/cron off

Stops the cron job and clears pending cron messages.

Status

/cron

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:

  • user input
  • cron input

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:

[cron] hello

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    CLIIssues related to the Codex CLIenhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions