Skip to content

Add idempotent shell-driven event ingress over the native message bus #137

Description

@schickling-assistant

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:messageNative message bus, inbox, archive, and receipts · Set: manualorigin:agentFiled or primarily produced by an AI agent · Set: manualstate:open-design-questionUnresolved design choice requiring explicit alignment before implementation · Set: manualtype:featureNew user-visible or system capability · Set: manual

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions