Communications bus for agentic workloads

Agents and humans, in conversations that survive the crash.

StarComms is one system with two roles. At the edge it is the gateway that terminates Slack, Discord, email, SMS, Telegram, and webhooks. Inside, it is the bus agents use to reach humans. Each channel adapter normalizes traffic into one envelope, each conversation runs as one durable workflow, and each outbound message carries a delivery state that says what is actually known about it. It is a communications bus, not a chatbot platform.

OUTBOUND SLACK PROVEN IN DOGFOOD · INBOUND AND OTHER CHANNELS SPECIFIED · NOT IN PRODUCTION

  • Temporal durable execution
  • One canonical envelope
  • Conversations as workflows
  • A delivery ledger on every channel
  • Tenant on every table
  • Per-tenant zero data retention
  • No second broker
The problem

Agent comms fail silently.

Most agent integrations treat messaging as glue code: ack the webhook, call the model inline, post the reply, hope. Each fault below comes from a documented ancestor, the hey-cammer Slack prototype that ran inside StarColony, or from the naive gateway designs it was built to escape.

FAULT 01

The swallowed reply

Inference runs inline after the webhook ack. The process dies mid-call. The user gets silence, and no record exists anywhere that a reply was ever owed.

FAULT 02

The double post

A retry lands after a timeout, on a post that actually succeeded. The send path has no delivery states, so the code can only guess whether to repeat it or drop it.

FAULT 03

The signature mistaken for authorization

A valid webhook signature proves which app sent the event, never which human. An integration that conflates the two spends someone’s budget for anyone who can reach the channel.

FAULT 04

The N×M rewrite

Each agent grows its own code for each channel. Conversation memory, dedupe, and retry get reimplemented, badly, once per pair.

These are not agent failures. They are communications failures, and communications failures deserve a dedicated harness.

One system, two roles

The bus and the envelope.

One canonical envelope at the edge, and one durable workflow per conversation on the bus. An agent answering a Slack thread and an agent asking a human a question use the same two primitives, in both directions.

Primitive A — the envelope

Agents never see channel JSON

Each channel adapter normalizes inbound traffic into one canonical envelope at the edge: verify, dedupe, ack, translate. Channel-native payloads stop at the adapter.

conversation  slack:{team}:{channel}:{thread_ts}
envelope_id   uuidv7 · one per logical message
sender        mapped principal · never trusted
delivery      signal-with-start → one workflow

One envelope per logical message, and the handle the delivery ledger tracks. Principal mapping is specified, not built; today the sender is the authenticated callback key.

Primitive B — the conversation

One durable workflow per conversation

The workflow ID is a deterministic conversation key, and each inbound message arrives by signal-with-start: deliver to the running conversation workflow, or atomically create it and deliver, in one operation. That one key gives four properties at once:

  • Thread ↔ workflow identity. No mapping table; the key is the identity
  • Conversation memory. Held in workflow state instead of reloaded per message (specified, not built yet)
  • Idempotent starts. A channel retry storm collapses to one workflow
  • Durable retry. The outbound post resumes after a crash instead of dropping the reply; the agent turn will ride the same path

It is a bus, not only a gateway. The same envelope and the same conversations carry agent-to-human messaging inside workloads, so “notify a human” and “answer a Slack thread” are one primitive rather than two code paths.

The channel roster

Seven channels, one contract.

Each adapter owes the bus the same five obligations: terminate channel auth, normalize to the envelope, claim the receipt, signal the conversation, run the delivery ledger. What differs per channel is the part worth designing for.

Slack

Status: OUTBOUND PROVEN · INBOUND SPECIFIED

Outbound posting runs in dogfood today through a typed, single-attempt Slack activity with the ledger behind it. Inbound is a rewrite of the proven StarColony gateway: same rejection ladder, same cryptographic invariants, and the durable path as the only path.

HTTP 200 + ok:false is still a failure · posts aren’t idempotent, so timeouts are genuinely ambiguous

Discord

Status: SPECIFIED

Gateway dial-out is proposed: the adapter connects outbound, so this channel may need no public callback endpoint at all.

threads are channels · user IDs are global snowflakes, unlike Slack’s

Email

Status: SPECIFIED

The weakest identity primitive in the roster: DKIM authenticates the sending domain, never the human. Principal mapping is load-bearing here.

threading from Message-ID / References · bounces arrive asynchronously, or never

SMS · Twilio

Status: SPECIFIED

There is no thread primitive; the number pair is the conversation. Synchronous success means queued, not delivered.

async status callbacks · the delivery ledger’s named revisit case

Telegram

Status: SPECIFIED

The webhook secret_token is a static shared secret rather than a body signature, so it is treated as a credential: constant-time compare, rotated like one.

webhook or long-poll · forum topics via message_thread_id

Webhooks

Status: SPECIFIED

Per-endpoint HMAC keys, mapped to principals, typically service or agent principals. Callers supply their own correlation IDs.

recovered-reply marker becomes a header: X-StarComms-Recovered

Web chat

Status: VIA CONSOLE — LATER

Rides the console’s authenticated session, the one channel where identity arrives as an OIDC principal and channel-identity mapping is the identity function.

no public path of its own · transport is the console’s decision

The differentiator

“Sent” is the most-lied-about word in messaging.

The delivery ledger has six named states that say what is known about an outbound message at each moment, including the moments when the true answer is “we do not know.”

Most chat APIs offer no idempotent post. After a crash mid-send, the system cannot know whether the user saw the reply. StarComms classifies that ambiguity instead of hiding it: each post runs as a workflow-managed, single-attempt activity whose typed result names the failure, and an ambiguous retry marks the message with a recovered-reply marker. A human can interpret a doubled message. They cannot interpret silence.

The ledger is the delivery-state contract for every channel, not a claim of identical semantics per channel. SMS delivery receipts are asynchronous, and that channel is the contract’s named revisit case.

Human-in-the-loop

Ask a human and durably wait.

Questions and approvals are first-class primitives rather than bolted-on notifications. An approval is a message, an envelope with a structured-action block that any channel adapter can render, parked on a durable workflow signal. A waiting approval holds no worker, no thread, and no open connection, however long the human takes. This section describes the specification; approvals are not built yet.

Decision records: authorization facts, never message content

  • verdict approved · principal jordan · channel slack bound to the subject digest; edits invalidate the approval
  • verdict approved_bypassed · requester = approver · in-context a distinct, audited verdict rather than a silent fast path
  • verdict denied_expired · no decision arrived silence confers no authorization; fail closed

The unusual case, approver-bypass when the approver is also the actor, is in the spec on purpose. A game admin playing their own game may bypass approval on their own feedback, but only when they hold the approver verb and the source system asserts they are actively in context. The proposed record for that path is approved_bypassed, a distinct verdict anyone can find later. The exact bypass semantics are still an open question in the register.

Policy, tenancy, retention

Small enough to audit by reading.

Each claim below names its mechanism. The chip says whether that mechanism is in the schema today or still in the specification.

  • Tenancy

    Multi-tenant in the schema, single-tenant in deployment today.

    • tenant_id on every table
    • tenant leads the unique keys
    • tenant-scoped policy (specified)
    Status: IN SCHEMA
  • Identity

    Channel identity is mapped, never trusted. A Slack ID or a From: address resolves to a principal, or it does nothing.

    • principal mapping
    • exact-match allow-lists as the floor
    • fail closed on empty
    Status: SPECIFIED
  • Policy

    Exactly two verbs: a group may converse with an agent; a group may approve for an agent. Principals are humans and agents alike. There is nothing finer to audit.

    • groups × two verbs × agents
    • one shape for every authorization question
    Status: SPECIFIED
  • Retention

    Per-tenant zero data retention: nothing message-shaped persists to the store, and store-dependent features degrade explicitly rather than silently.

    • ZDR gate upstream of the INSERT
    • memory in encrypted workflow state
    • delivery states survive, content-free
    Status: SPECIFIED
Provenance

The harness, not the platform.

StarComms runs on StarColony and does not reimplement it. The division of labor is a hard boundary.

StarComms owns

Workflows reside here
  • Conversation and delivery workflow definitions, and the workers that run them
  • Channel adapters and channel-scoped credentials
  • Its own Postgres; comms state never mixes with the platform’s
  • Its own Kubernetes namespace
  • Principal mapping and the two-verb policy tables (specified)

StarColony owns

The backbone is there
  • The Temporal cluster; StarComms runs its own task queues and workflow types inside it
  • The agent runtime and the model plane
  • Tool governance, budgets, and audit
  • Every model call. StarComms never calls a model provider directly, so no provider key lives in StarComms, and the bus crates hold no model credential at all

There is no second broker. Temporal is the delivery backbone, with no Kafka and no separate distributed log, because a second log would add an at-least-once seam between two systems that each think they own delivery.

Status survey

What runs, and what is under construction.

Present tense on this page describes the architecture. Here is where the build is as of September 2026.

Running: the outbound dogfood slice

  • Nine Rust crates. The bus is an axum API plus a Temporal worker, with its own Postgres and Kubernetes namespace, on a local kind cluster
  • Signed StarColony run-lifecycle callbacks accepted with HMAC verification, a replay window, and active and retiring keys
  • Content-free receipts collapse redelivery to exactly one signal-with-start per event
  • One deterministic conversation workflow per Slack thread, with a typed single-attempt post activity and the six-state ledger behind it
  • Started and completed events landed in one supplied Slack thread on 2026-08-11
  • Lineage: the hey-cammer Slack gateway that ran inside StarColony. Its inbound code is untouched, and its app identity was reused for the dogfood

Also true

The six-state ledger is a default contract, not a proven universal. SMS delivery receipts are asynchronous, the named likely exception. If SMS semantics do not fit the six states, the contract gets amended rather than “delivered” quietly overloaded.

The first deployment is single-tenant, even though the schema is not. Multi-tenancy today is a schema commitment; making a second tenant a matter of configuration is why the cost is paid now.

ZDR is a statement about the store, not a magic eraser. Content still transits codec-encrypted workflow state with a bounded retention window. A tenant for whom that is unacceptable cannot be served by this architecture.

The repository holds more than the bus. Two adjacent subsystems for agent-to-agent messaging, one over git vaults and one over a tailnet mesh, live in the same workspace with their own message formats. They are outside this page by decision, and converging them on the bus envelope is an open question.

Contact

Talk to us before the signup exists.

If you run message delivery for a living, or you want your agents talking to humans on channels that do not drop replies, we want the conversation before we want the signup.

Early conversation
STAGEoutbound Slack in dogfood · rest specified
FIRST CHANNELSlack: outbound proven, inbound next
SUBSTRATEStarColony · shared Temporal, own Postgres
CONSOLElater; web chat lands there
Start the conversation

What you get is a conversation about your delivery problems and the open questions in the register.

Delivered, or a named failure.