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.
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
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.
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.
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.
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.
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 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.
Each channel adapter normalizes inbound traffic into one canonical envelope at the edge: verify, dedupe, ack, translate. Channel-native payloads stop at the adapter.
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.
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:
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.
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.
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
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
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
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
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
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
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 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.
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.
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.
Each claim below names its mechanism. The chip says whether that mechanism is in the schema today or still in the specification.
Multi-tenant in the schema, single-tenant in deployment today.
Channel identity is mapped, never trusted. A Slack ID or a From: address resolves to a principal, or it does nothing.
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.
Per-tenant zero data retention: nothing message-shaped persists to the store, and store-dependent features degrade explicitly rather than silently.
StarComms runs on StarColony and does not reimplement it. The division of labor is a hard boundary.
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.
Present tense on this page describes the architecture. Here is where the build is as of September 2026.
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.
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.
What you get is a conversation about your delivery problems and the open questions in the register.
Delivered, or a named failure.