Software EngineeringAutomation

Schedule Codex Routines as Finite Runs

How my Mattermost bridge keeps recurring schedules outside Codex, gives each run explicit state, and records the result without leaving an agent goal alive.

Workflow diagram linking Mattermost chat, a clock scheduler, the Codex bridge, stored records, and an image output.
Lead imageWorkflow diagram linking Mattermost chat, a clock scheduler, the Codex bridge, stored records, and an image output.
On this page

In my Mattermost setup, a recurring agent job has a deliberately small boundary: the bridge decides when to start, and Codex completes one run.

Recurrence does not belong inside the agent conversation. A prompt such as “do this every morning” leaves Codex responsible for work it cannot schedule by itself and gives the thread no natural point at which to finish. The bridge already has a scheduler, so each scheduled event can create a finite Codex run, record its result, and stop.

Workflow linking Mattermost, a scheduler, the Codex bridge, stored records, and an image output
The bridge owns recurrence; Codex owns one finite run.

Keep the boundary explicit

An ordinary Codex thread is well suited to finite instructions:

fix this
test that
summarize this run
generate today's lesson

A recurring workflow needs information outside that task:

when the next run starts
where its result is posted
whether it gets a new Mattermost thread
which durable state it reads and updates
where the bridge records the run

Those are scheduler concerns. In this setup, the Mattermost bridge stores them in a routine definition and checks for due routines once a minute by default. When one is due, it creates a Mattermost anchor, records a queued run, and sends Codex a prompt that identifies the routine, run, destination, and state path.

The wrapper also tells Codex that this is a routine rather than a goal:

You are executing a scheduled Mattermost Codex routine.
This is not a Codex goal.
Do one finite run of the routine, report the result,
and leave recurrence to the bridge scheduler.

That instruction is not a substitute for a good task prompt, but it makes ownership clear. The bridge will arrange the next run. This Codex turn only needs to finish the current one.

Create the routine from Mattermost

The bridge exposes routine management through one slash command:

/routine list
/routine show <id>
/routine create <id> [options] -- <prompt>
/routine run <id>
/routine pause <id>
/routine resume <id>
/routine delete <id>

Here is a daily teaching routine:

/routine create daily-algo \
  --schedule daily \
  --time 08:00 \
  --target dm \
  --thread new \
  --state ~/routines/algorithms/catalog.md \
  -- Teach one algorithms lesson for today. Include code and 2-3 practical use cases, generate one simple diagram, attach the image to the final Mattermost reply, update the catalog, and stop.

The options answer the scheduling questions: run daily at 08:00, post to a direct message, create a new thread, and expose the catalog path to the run. Everything after -- is the work Codex must complete once.

I would not wait until 08:00 to discover that the prompt is incomplete. A manual run uses the same execution path as a scheduled one:

/routine run daily-algo

The test is useful only if I inspect its effects. Did the run read and update the catalog? Did the final answer land in the expected thread? If the prompt requested an image, did Mattermost receive an actual attachment? A successful text reply does not prove that the whole workflow succeeded.

Run more than once per day

A daily routine can have several local times. For example:

/routine create tech-news \
  --schedule daily \
  --times 06:00,13:00,17:00 \
  --target here \
  --thread new \
  -- Read the newsletter guide, resolve today's state file, produce this edition, update the state, publish the result, and stop.

The bridge calculates the next due time after each scheduled start. It does not keep one agent run alive between 06:00 and 17:00; those are three separate runs.

If the editions need same-day memory, the prompt can tell each run to resolve a dated file:

STATE_FILE="$HOME/routines/tech-news/state-$(TZ=Africa/Nairobi date +%F).json"

All three runs can then share today’s selections without carrying yesterday’s state forward. The state convention belongs to the workflow; the scheduler only passes the configured state path and prompt to Codex.

Separate work state from run receipts

The bridge stores routine definitions and recent run receipts in:

~/.local/state/mattermost-codex-bridge/routines.json

A receipt records operational facts such as the routine id, trigger, status, start and finish times, Mattermost root, Codex thread, summary, and error. That answers “did this run execute, and where did it go?”

The working state answers a different question: “what must the next run remember?” I keep that in ordinary files owned by the workflow, for example:

~/routines/algorithms/catalog.md
~/routines/tech-news/state-2026-06-12.json

Keeping the two separate makes both easier to inspect. I can delete or rebuild a lesson catalog without editing scheduler records, and I can examine failed-run history without treating it as editorial state.

Choose a thread policy deliberately

For daily artifacts, I usually want --thread new. Each run gets a new anchor and a self-contained place for its final result, supporting replies, and attached files.

--thread reuse is better when the conversation itself is the durable context. The tradeoff is that several runs accumulate under one root, so it becomes harder to treat a thread as the receipt for a single run.

Images need one more check. A generated file on the server is not yet a Mattermost attachment. At the end of a run, the bridge collects image-generation outputs, uploads those files through the Mattermost API, and attaches the returned file ids to the final reply. When an image matters, I verify the attachment in the thread rather than trusting the local path alone.

Know when this small scheduler is not enough

Cron can start a command, but it has no built-in understanding of Mattermost threads, Codex runs, or image attachments. It is still a reasonable choice when the job is simply a local command and chat delivery is irrelevant.

A workflow engine earns its extra machinery when a process needs branching, retries with policy, approval gates, dashboards, or coordination across many operators. The Mattermost bridge does not try to provide those features.

Its useful constraint is narrower: define when a run starts, give that run enough context to finish, record what happened, and then get out of the way. The scheduler can come back tomorrow. The agent should not have to wait for it.

Continue reading

Complete index →