Skip to content

Turn pipeline

Every AsteronIris surface that executes a companion turn converges on the same turn contract. That sentence is easy to say and costly to maintain; it is also the mechanical backbone of the continuity claim. Discord text is the product-proven channel. Other channels and operator surfaces reuse the contract where they create or replay turns, but they are not all equally mature product surfaces.

[Accepted turn: Discord / CLI / gateway / operator surface]
Pickup / ingress policy
Turn enrichment
affect detection → memory recall → persona context
Response assembly + tool loop
Pre-send verification
Reply delivery
Post-turn update
relationship memory · persona drift · continuity cues

The transport-facing path lives in src/runtime/services/companion_turn.rs. Pre/post-turn enrichment is owned by src/core/agent/turn_enrichment.rs. The tool loop runs every tool call through a fixed middleware chain:

SecurityMiddleware → TaintMiddleware → AuditMiddleware → EntityRateLimitMiddleware

Three reasons the project pays the maintenance cost of unification:

  • Continuity cannot silently diverge by surface. If Discord-side enrichment and CLI-side enrichment drifted apart, the companion would become a different entity depending on where you talked to it. That is the exact failure mode the runtime is built to prevent.
  • Verification is a choke point, not a decoration. Pre-send verification has to be unskippable. A second pipeline is a second chance to forget to run it.
  • Behavior is reproducible. The same accepted turn walks the same code regardless of where it originated. That makes evals, replay, and incident triage possible; otherwise every surface is its own debugging surface.

Not every inbound event becomes a turn. The pickup / ingress policy — run before enrichment — decides:

  • Is this message directed at the companion, or ambient chatter?
  • Has the companion already said enough in this channel recently?
  • Is the room public, private, or a thread, and does that change the distance the companion should keep?

That decision is intentionally before memory recall and persona context. A companion that runs enrichment on every inbound event burns budget and also reads as eager and boundary-less. The pickup gate is a character constraint as much as a performance one.

The tool loop is a subroutine, not the product

Section titled “The tool loop is a subroutine, not the product”

When a tool call is needed mid-turn, the tool loop spins inside response assembly. It is governed, audited, rate-limited, and taint-tracked. None of that leaks into the “shape” of the turn from the user’s perspective; a turn with tool calls and a turn without both exit through the same pre-send verifier and the same post-turn update.

This is deliberate. A companion that visibly switches modes — “now I am an agent, now I am chatting” — has two personalities. AsteronIris has one.