A User Message Is More Than a User Message
When a user types a message in an AI-native workspace, the system sees far more than text. We unpack Kylon's activation architecture and show how a single message becomes a structured execution contract.
In most AI products, sending a message is straightforward. You type something, the system passes your text to a model, and the model responds. The message is the message. What you typed is what the system sees.
In Kylon, that assumption doesn't hold.
When a user sends a message in a channel, the system doesn't just relay text to an AI model. It constructs a structured execution contract called an activation. That activation carries the user's message, yes, but also the full operational context the agent needs to act correctly: who triggered the work, what permissions are delegated, which session scope applies, how deep the autonomous chain has gone, and what kind of response is expected.
This is the story of why we built it this way, and what it makes possible.
The problem with passing messages
The simplest possible architecture for an AI workspace is a pipe: user text goes in, model text comes out. This works fine for chatbots. It breaks down the moment agents become real participants in a team.
Consider what happens when a human sends "@Ryan please review the quarterly report" in a channel:
- Which agent should handle it? (There might be several in the channel.)
- Does the agent have access to the file being referenced?
- Can it use the sender's connected Google Drive to fetch the document?
- Is this a top-level request or a reply deep inside a thread?
- If the agent triggers another agent to help, how deep can that chain go before we stop it?
- Should the response be public or stay internal?
A raw message string answers none of these questions. The system has to answer all of them before the agent's first token is generated.
Figure 1. A user message is transformed into a SessionPendingActivation, then progressively filtered through Redis, PostgreSQL, and the runtime before the model sees a curated set of session entries.
Activation: the execution contract
Every piece of work in Kylon starts with a SessionPendingActivation. This is not just a wrapper around the user's text. It is the canonical contract that tells the execution engine exactly what to do.
Every activation carries a common set of fields:
| Field | What it answers |
|---|---|
| kind | What type of work is this? A message, a workflow trigger, a follow-up, a voice call result? |
| source | Did a human, another agent, or the system initiate this? |
| depth | How many agents deep is this autonomous chain? |
| sessionKind | Is this a main channel session, a thread, a workflow, or a sub-agent? |
| delegationUserId | Whose connection grants can this agent borrow? |
| publicOutput | Should the result be visible in the channel? |
These fields exist on every activation, regardless of kind. They form the operational envelope that makes safe, scoped execution possible.
Figure 2. The full lifecycle of a message activation. The user's message is stored once and referenced by ID. Content is loaded at session preparation time, not duplicated in the queue payload.
Not all activations are messages
Here is where the design gets interesting. A "user message" is just one of many activation kinds. The system treats a scheduled workflow, a follow-up reminder, a sub-agent returning results, and a voice call transcript with the same contract structure. They differ only in their kind-specific payload.
Figure 3. The common activation contract branches into kind-specific payloads. A message carries a messageId; a workflow carries its task and trigger context; a sub-agent result carries a summary.
| Kind | What it carries |
|---|---|
| message | A message ID (not the text itself), mention mode, sync settings |
| thread_message | Message ID plus thread root, so the agent knows exactly where in the conversation it is |
| workflow | The workflow definition, task message, trigger type, and correlation IDs |
| agent_trigger | Arbitrary context for direct agent-to-agent invocations |
| followup | A follow-up ID and claim token for scheduled check-backs |
| subagent_result | The parent session ID and the sub-agent's result summary |
| voice_call_result | Voice task ID and the transcribed result |
References vs. content: a design choice
Notice something important: message activations carry references, not content. The activation stores a messageId, not the message body. The actual text, along with surrounding conversation context, is loaded during session preparation.
Workflow and direct-trigger activations are different. They carry their task content directly, because there is no pre-existing message to reference.
Figure 4. The two activation strategies. Reference-based activations keep the payload small and defer content loading. Content-carrying activations are self-contained for system-initiated work.
This is a deliberate design choice. Reference-based activations let the content loading logic apply its own access checks and context windowing independently. Content-carrying activations are self-contained because there is no pre-existing message to look up.
Four layers, four different views
An activation doesn't stay in one place. It flows through four distinct layers, and each layer stores a different subset of the data.
Figure 5. The same activation is represented differently at each layer. Redis needs the full payload for routing. PostgreSQL keeps a durable audit ledger. The runtime derives a scoped context for execution. The model sees formatted session entries.
Redis holds the complete SessionPendingActivation because the execution engine needs every routing and kind-specific field to dispatch the work correctly. It also selects a priority lane: interactive work gets the fast lane; workflows and follow-ups go to the responsive lane; background maintenance goes to the background lane.
PostgreSQL stores a durable execution ledger. It does not copy the full activation. Instead, it records the facts needed for audit and correlation: who triggered the work, when it started, when it finished, whether it succeeded, and what error occurred if it didn't. It deliberately omits transient state like orchestration positions and message sync configuration.
The runtime derives a compact ActivationContext that execution code, tools, usage attribution, and nested activations all share. This context carries per-activation resource limits (runtime, LLM turns, tool calls), schema constraints for structured output, and model selection. The full queued activation is not copied here; the runtime extracts only what it needs.
The model sees none of the above directly. Session preparation derives typed entries from the activation: an activation context entry with source, locale, timezone, and reply routing; a trigger context entry for workflows and direct triggers; thread context and channel message context loaded from referenced messages. The model works with formatted, human-readable session entries, not raw system objects.
Why references instead of copies
A recurring pattern in this design is indirection. The activation carries a message ID, not the message. The runtime derives a context, not a copy. The model sees formatted entries, not raw payloads.
This is not accidental. Each layer of indirection solves a specific problem:
Access control at load time. When the system loads message content from a reference, it can check whether the current agent actually has access to that message, that channel, that thread. If the activation carried the content directly, those checks would have to happen at activation creation time, and the access state might have changed by the time the agent runs.
Context windowing. The model doesn't need every message in a 500-message thread. By loading content at preparation time rather than embedding it in the activation, the system can apply intelligent windowing: recent messages in full, older messages summarized, irrelevant branches pruned. The activation stays small; the context gets tuned.
Isolation between execution stages. The Redis queue, the PostgreSQL ledger, the runtime context, and the model prompt each serve a different audience with different trust levels. By not copying the full activation everywhere, each layer can enforce its own invariants. The model prompt, for instance, never sees raw orchestration state or delegation user IDs. It sees the formatted consequences of those fields.
Orchestration: when multiple agents collaborate
The activation contract also carries optional orchestration state for multi-agent scenarios. When several agents participate in a channel discussion, the system tracks whose turn it is, whether anyone has replied, and when to stop.
Figure 6. The orchestration state machine. Agents never coordinate directly. The system dispatches one agent at a time, tracks the no-reply streak, and halts when either an agent responds or all agents pass.
This state lives in the activation, not in any single agent's memory. That means the orchestration logic is a system-level concern, not something individual agents have to reason about or coordinate on their own.
What this makes possible
This architecture enables behaviors that would be difficult or impossible with a simple message-passing design:
Graceful delegation. When a user asks an agent to do something that requires another user's Google Drive access, the activation's delegationUserId field lets the system check and forward that user's connection grants without the agent ever seeing raw credentials.
Depth-limited autonomy. The depth field means the system can enforce hard limits on agent-to-agent chains. If Agent A triggers Agent B, which triggers Agent C, the system knows exactly how deep the chain has gone and can halt it before it spirals. This is a guardrail that lives in the contract, not in the agents' prompts.
Priority-aware scheduling. Because the activation knows its own kind and session type, the queue can make intelligent scheduling decisions. A human waiting for a reply gets the fast lane. A nightly report workflow gets the responsive lane. A background health check gets what's left. No manual priority configuration needed.
Auditable execution. Every activation gets a durable record in PostgreSQL with timestamps, status, and error tracking. When something goes wrong, you can trace the exact activation that caused it, see who triggered it, and understand the full execution chain without reconstructing it from log files.
Safe context for the model. The model sees carefully formatted session entries, not raw system internals. It knows the activation source, the user's locale and timezone, the mention behavior expected, and any relevant trigger context. It does not know about Redis lanes, orchestration indices, or delegation mechanics. This separation means the model can focus on the task without being confused or manipulated by system metadata.
The design principle
The underlying principle is simple: the further data travels from the system boundary toward the model, the more it should be filtered, formatted, and scoped.
At the outermost layer (Redis), the system needs everything. At the innermost layer (the model prompt), it needs only what helps the agent do its job well. Every intermediate layer exists to make that reduction safely and correctly.
This is the opposite of how most AI integrations work, where the model gets whatever the system has and is expected to figure out what matters. That approach is fine for chatbots. It is not fine for agents that manage real data, use real tools, and act on behalf of real people.
A user message, it turns out, was never just a user message. It was always an execution contract waiting for a system that could read it properly.
Hire the AI agent team that runs your entire business.