Problem
st2 has a durable native message bus and DING delivery, and st2 message send <recipient> already accepts a body on stdin. That is enough for a one-off shell producer, but not yet a reliable external-event ingress contract.
Recurring timers, GitHub/webhook adapters, monitoring alerts, and similar producers need to turn an external event into exactly one durable inbox item. Today, retrying message send creates a new message each time, and the resulting message has no first-class source event identity that can serve as a receipt and correlation key.
A concrete motivating case is a Nix-managed daily operational check: a systemd timer should emit one event per local calendar day to a standing agent; the inbox write should wake the agent through the existing DING path. The same primitive should work for GitHub delivery IDs and monitoring events without teaching st2 about cron or GitHub.
Proposed ownership boundary
Bootstrap external producers may own when and how an event occurs:
- systemd/launchd/cron timers (bootstrap until native recurring sources exist)
- GitHub webhook or polling adapters
- monitoring and incident sources
- arbitrary shell pipelines
st2 owns the durable ingress boundary and the minimal recurring-source lifecycle:
-
declare and supervise a recurring shell-driven source so bootstrap host timers can be removed
-
parse a small event envelope from CLI flags and/or stdin
-
route it to an agent inbox
-
deduplicate retries by (source, event-id)
-
return a stable machine-readable receipt
-
let the existing DING sidecar wake the recipient
This could be exposed as st2 event emit, st2 pipe, or an extension of st2 message send; the command name is intentionally open.
Minimum useful interface
Conceptually:
check_dev3 | st2 event emit dev3.janitor \
--source systemd:dev3-janitor-daily \
--event-id 2026-07-31 \
--subject 'Daily operational check' \
--json
A GitHub adapter could use github:<delivery-id> as the source/event identity. Repeating the command with the same identity should return the original receipt and must not create or DING a second inbox item.
The durable inbox item should retain enough provenance to answer:
- which producer emitted it?
- what external event ID caused it?
- which st2 message/receipt represents it?
- was this invocation newly accepted or deduplicated?
Acceptance criteria
- A shell command can emit an event to a catalog identity using stdin for the payload.
- A native recurring shell-driven source can emit on a declared cadence with persistent catch-up semantics, allowing a bootstrap system timer to be removed.
- The producer supplies a stable source and event ID.
- Replaying the same
(source, event-id) is idempotent, including concurrent retries.
- JSON output includes at least recipient, message/receipt ID, and
created versus deduplicated outcome.
- A newly accepted event follows the normal inbox -> DING path; a duplicate does not cause another DING.
- The on-disk representation remains compatible with normal message list/read/archive/thread workflows, or the CLI clearly defines the join between an event receipt and its message.
- Tests cover retry, concurrent duplicate emission, and a producer crash/retry boundary.
Non-goals
- A general-purpose cron or workflow engine. The minimal recurring event source above is in scope.
- Embedding GitHub polling/webhook logic in st2.
- A general workflow engine.
- Replacing the native message bus or DING transport.
Open design questions
- Extend message frontmatter with
source / event-id, or keep a separate receipt index joined to the immutable message?
- Should deduplication live per recipient/catalog, or at a shared bus scope?
- What retention rule applies to dedupe receipts after the inbox message is archived?
- Is
event emit a clearer user model than pipe, given that stdin piping already works for ordinary messages?
Posted on behalf of @schickling
| field |
value |
agent_name |
co3-cobalt |
agent_session_id |
b9db6156-4852-4586-87da-29efe25f759f |
agent_tool |
Codex CLI |
agent_tool_version |
0.145.0 |
agent_runtime |
Codex CLI 0.145.0 |
agent_model |
unknown |
runtime_profile |
/nix/store/mnx8agbdq3wiyb6vz63lhgscgazkrn98-coding-agent-runtime-profile/share/coding-agents/profile.json |
skills_manifest |
/nix/store/5r69m9k2llmri3na81518zx0a7y0d3cn-agent-skills-corpus/share/agent-skills/manifest.json |
worktree |
dotfiles/schickling/2026-07-20-cos-misc |
machine |
dev3 |
tooling_profile |
dotfiles@0fb7e03 |
Problem
st2 has a durable native message bus and DING delivery, and
st2 message send <recipient>already accepts a body on stdin. That is enough for a one-off shell producer, but not yet a reliable external-event ingress contract.Recurring timers, GitHub/webhook adapters, monitoring alerts, and similar producers need to turn an external event into exactly one durable inbox item. Today, retrying
message sendcreates a new message each time, and the resulting message has no first-class source event identity that can serve as a receipt and correlation key.A concrete motivating case is a Nix-managed daily operational check: a systemd timer should emit one event per local calendar day to a standing agent; the inbox write should wake the agent through the existing DING path. The same primitive should work for GitHub delivery IDs and monitoring events without teaching st2 about cron or GitHub.
Proposed ownership boundary
Bootstrap external producers may own when and how an event occurs:
st2 owns the durable ingress boundary and the minimal recurring-source lifecycle:
declare and supervise a recurring shell-driven source so bootstrap host timers can be removed
parse a small event envelope from CLI flags and/or stdin
route it to an agent inbox
deduplicate retries by
(source, event-id)return a stable machine-readable receipt
let the existing DING sidecar wake the recipient
This could be exposed as
st2 event emit,st2 pipe, or an extension ofst2 message send; the command name is intentionally open.Minimum useful interface
Conceptually:
A GitHub adapter could use
github:<delivery-id>as the source/event identity. Repeating the command with the same identity should return the original receipt and must not create or DING a second inbox item.The durable inbox item should retain enough provenance to answer:
Acceptance criteria
(source, event-id)is idempotent, including concurrent retries.createdversusdeduplicatedoutcome.Non-goals
Open design questions
source/event-id, or keep a separate receipt index joined to the immutable message?event emita clearer user model thanpipe, given that stdin piping already works for ordinary messages?Posted on behalf of @schickling
agent_nameagent_session_idagent_toolagent_tool_versionagent_runtimeagent_modelruntime_profileskills_manifestworktreemachinetooling_profile