Create a KDCube ReAct Agent
An app creates a ReAct agent by declaring it: built fresh every turn from app code, the admin ceiling, the user’s narrowing, and durable state.
An app creates a ReAct agent mostly by declaring it.
There is no agent process to keep alive between messages. For every turn,
KDCube builds a new runtime object from four inputs: app code
(build_react), admin config (the ceiling), the conversation
choice (the user's narrowing), and durable turn state (timeline and
context).
"Built fresh" does not mean "forgets everything." The runtime object is new; the conversation timeline, working summaries, user settings, workspace refs, memory, and event-lane state are durable inputs.
This article follows the current pipeline end to end: configuration, conversation-scoped capabilities, tool traits, demand-driven consent, event sources, subagents, prompt-cache governance, and the live context the model actually receives.
01 The contract in one line
The administrator declares the maximum agent. The user narrows that agent for a conversation. The runtime enforces the remaining policy at the exact boundary where an operation is attempted. Those are different responsibilities:
| Authority | Decides | When it acts |
|---|---|---|
| App code | How the workflow composes and runs ReAct. | Construction and execution. |
| App config | Which models, tools, skills, services, event sources, and subagents may exist. | Inventory resolution. |
| User selection | Which offered capabilities this conversation should use, plus one allowed model pick. | Explicit Save, then turn start under cache policy. |
| Runtime fences | Whether this concrete call has the required role, claim, consent, budget, and valid parameters. | At operation attempt. |
effective capability ≤ administrator inventory — user
settings can remove from the configured inventory. They cannot invent a
tool, model, skill, service operation, named action, or helper-agent
ability that the app did not offer.
02 The construction pipeline
The Workspace reference app exposes the complete path. Its ReAct node resolves the inventory, applies the conversation selection, composes teaching, builds the runtime, and runs it:
Two details prevent common misreadings of this diagram.
First, user selection and delegated consent are not the same kind of filter. A user-disabled tool leaves the turn's catalog. A configured tool that needs a connected account stays visible and callable; if the model actually attempts it without the required grant, dispatch returns a structured consent result.
Second, selection-store failure is fail-open only to the configured inventory. It does not bypass tool authorization. Roles, delegated claims, economics, input validation, and provider policy are still enforced at the operation boundary.
Model resolution is also turn-aware without rebuilding app config. The
user's validated model pick overlays the configured strong decision role on
runtime_ctx.agent_role_models; the model router resolves that
role when the call is made.
03 Two config roots shape one agent
The runtime reads behavior and inventory from separate parts of the app
entry in bundles.yaml:
config.react behavior, models, teaching, subagents config.surfaces.as_consumer tools, skills, MCP, named services, event sources and their policy
ReAct behavior resolves through the agent id, its filesystem-safe form,
default_agent, default, and finally the ReAct
root. The resolver recognizes both direct per-agent blocks and the explicit
react.agents.<id> map. This lets an app place shared
defaults once and override only the agents that differ.
The current Workspace configuration is a useful reference because it exercises nearly every surface:
config: react: agents: main: subagents: allowed: true visibility: thread model: strong_agent default_agent: event_source_pipeline: enabled: true supported_models: - provider: anthropic model: claude-sonnet-4-6 label: Sonnet 4.6 - provider: anthropic model: claude-haiku-4-5-20251001 label: Haiku 4.5 additional_instructions: | [KDCUBE DOC LINKS] Present repo: references as browsable GitHub URLs. surfaces: as_consumer: default_agent: main agents: main: tools: - kind: python alias: exec_tools module: kdcube_ai_app.apps.chat.sdk.tools.exec_tools allowed: [execute_code_python] tool_traits: execute_code_python: strategy: [exploration, exploitation] - kind: named_service alias: named_services namespaces: mem: allowed: [provider.about, object.search, object.schema, object.upsert, object.action, object.delete] cnv: allowed: [provider.about, object.list, object.search, object.schema, object.upsert] mail: allowed: [provider.about, object.list, object.search, object.schema, object.action, object.host_file] - kind: mcp server_id: knowledge alias: knowledge allowed: ['*'] tool_traits: '*': strategy: [exploration] event_sources: - kind: named_service namespace: mem discovery: {{mode: service_discovery}} policies: block_production: mode: provider operation: block.produce pull: mode: provider operation: object.get
This is not boilerplate. Each field answers a runtime question:
allowed is the administrator's callable ceiling;
tool_traits tells both the model and runtime what an action
means; namespace allowed lists the exact grammar operations
this agent may use; event_sources connects owner-defined
discovery, timeline-block production, and pull behavior to the ReAct
event/source pipeline; subagents decides whether delegation can
be installed at all; supported_models is the user's bounded
price/quality menu, while role_models remains the app's default
role routing. The configuration is therefore an executable policy plane, not
merely a list of function names.
04 Tool traits make the inventory operational
A JSON schema explains how to call a tool. It cannot tell the runtime whether the call only gathers evidence, changes durable state, or may begin while the model is still generating later actions. Tool traits provide that missing meaning:
exploration search, read, inspect, fetch, pull exploitation write, patch, upsert, delete, render, host neutral independent bookkeeping or detached scheduling unknown usable, but conservatively ordered
The multi-action overseer reads these traits. For example, it rejects an exploration followed by an exploitation in the same generated round when the write claims to depend on evidence that has not run yet. A complete detached call may also carry an explicit execution profile that permits tracked, at-most-once early execution while generation continues.
Traits can originate in Python decorators, built-in tool specs, or the per-agent connection config shown above. Python, MCP, and named-service tools all converge into the same metadata plane. The model sees the explanation in its catalog; correctness does not depend on the model obeying prose because the runtime reads the same metadata independently. For the complete ordering matrix and early-execution contract, see Tool Traits: Tell the Runtime What a Tool Means.
05 Consent is demand-driven, not a turn-start sweep
Connected-account requirements are attached to individual tool operations. The runtime intentionally does not preflight every configured claim and drop every unavailable tool before the model starts.
Why? A rich agent may expose mail, Slack, tasks, memory, and other services, while one request needs only a memory search. Asking for every possible consent up front would be noisy, slow, and semantically wrong. The needed authority is known only when an operation is attempted.
configured tool stays in catalog
↓
model attempts concrete operation
↓
claim satisfied → ordinary fenced dispatch
claim missing → structured consent envelope
+ scoped chat consent event
+ demand recorded for this conversation
If the user connects or approves the account, the next turn checks only
the claims that were actually demanded earlier. Newly satisfied tools are
recorded on runtime_ctx.reactivated_tools and rendered in
ANNOUNCE so current truth overrides the model's older statement that access
was unavailable.
The turn-start consent hook is deliberately best-effort and budgeted because it is transition bookkeeping, not the security fence. A timeout leaves the configured set intact; the operation-level claim gate still decides whether a call may cross the boundary. This same separation is why a named service can remain a generic grammar while its provider owns domain claims and actions — see Named Services: Ontologic Tools.
06 Instructions and ANNOUNCE have opposite lifecycles
The rendered model input has two homes for platform-supplied information:
Is this durable teaching, or is it the current situation? Durable teaching belongs in the cached instruction. Turn state belongs in ANNOUNCE.
The model sees both, but only the second may change every decision round without rewriting the cached system prefix. An unmet connected-account claim is no longer predicted in ANNOUNCE before an attempt. The attempt itself returns the consent result. ANNOUNCE is used later for the transition that matters to ongoing reasoning: a previously demanded tool has become available.
07 The user tunes a conversation, deliberately
The administrator inventory is a ceiling. The user's capability picker edits a local draft for the current conversation and persists it only when the user presses Save changes.
application fallback or user baseline
↓ materialize once for a new conversation
conversation selection
↓ local picker draft → Save changes
agent_selection_update(conversation_id)
↓ clamp against live inventory
turn-start narrowing and model/subagent application
An independently mounted capabilities widget with no conversation id manages the user's baseline for future conversations. A chat picker never silently rewrites that baseline. The stored record is a deny-list plus one bounded model pick:
{{
"schema_version": 1,
"disabled": {{
"tools": {{"web_tools": ["web_fetch"]}},
"mcp": {{"knowledge": ["kb_fetch"]}},
"named_services": {{
"mail": ["object.action.send"],
"task": ["object.delete"]
}},
"skills": ["public.docx-press"],
"subagents": true
}},
"model": {{
"provider": "anthropic",
"model": "claude-haiku-4-5-20251001"
}}
}}
The granularity is intentional:
| User choice | Runtime effect |
|---|---|
| Tool group or individual tool | Removed from the turn's resolved tool config. Locked system groups remain on. |
| MCP server or MCP tool | Removed from that connection's effective set. |
| Named-service realm | Removed from both roster and dispatch. |
| Realm operation | That generic operation is rejected at named-service dispatch. |
| Named action | Only that domain action is rejected; sibling actions remain available. |
| Skill | Removed from the skill gallery and consumers. |
| Subagents | Spawner is not installed; react.delegate and its guidance are absent. |
| Model | Validated against supported_models, then overlaid on the strong decision role. |
Writes clamp against the live administrator inventory, and reads narrow again. A stale row cannot widen a later config. Missing or unreadable selection state falls back to configured behavior so settings storage cannot silence the agent; operation-level policy remains in force.
The durable storage and scope model is covered in One home for what the user decided.
08 Capability changes have a real cache cost
ReAct sends one cached system block before the conversation messages. That block contains protocol, instructions, tool catalog, skill gallery, and namespace teaching. This layout makes the consequence precise: a tool, skill, MCP, namespace, or subagent change colds the whole prompt for one turn; a model switch is fully cold because provider prompt caches are model specific; ANNOUNCE-only state never invalidates the stable prefix.
The platform does not hide this cost. A conversation persists the
last-applied selection snapshot and its cache-warm signal. When a change
applies to a warm conversation, RuntimeCtx.cold_turn_marker
produces a [CACHE] ANNOUNCE line, accounting metadata
identifies the cold turn, and logs correlate the change with the cache
attempt.
The paying user controls when a selection change lands. The standing
policy is one of accept, confirm (the platform
default), defer_cold, or defer_conversation,
bounded by administrator config. Under confirm, Save offers
apply now, apply from the next conversation, or apply when this
conversation's cache is already cold. Deferred deltas are promoted when
their trigger becomes true. This policy exists today; it is not a future
warning mechanism.
09 Subagents are a capability, not a separate agent product
When the app offers subagents and the conversation has not disabled them,
build_react installs a subagent spawner. That installation is
what adds react.delegate to the decision catalog and delegation
guidance to the instruction. If the ability is absent or denied, the turn
carries neither.
A delegation is not an in-process callback. It creates a child conversation and schedules a first-class child turn with its own model calls, economics boundary, timeline, persistence, and refs. The parent keeps working. Child contributions and completion return through the parent conversation's event lane and fold into a live or promoted follow-up turn.
That is why the config exposes both administrative defaults and a user toggle: helpers can improve difficult work, but they also spend more. The administrator offers the capability; the paying user decides whether this conversation may use it. The full scheduling and timeline contract is in Subagents: Delegate the Hard Part, Keep Working.
10 Event sources are not model-callable tools
Tools and event sources meet inside ReAct, but they enter from opposite directions:
tool call: model → runtime fence → provider → result → timeline event source: provider/event lane → block-production policy → timeline → model
The Workspace app declares named-service event sources for tasks, memory,
mail, and Slack. Discovery locates the owner. Block-production policy
decides which model-visible timeline blocks an accepted event creates. Pull
policy tells react.pull how to ask the owner for exact content
without pretending owner refs are local files.
With event_source_pipeline.enabled, a reactive turn can fold
accepted events into its shared timeline while it is still working. The
construction config does not itself transport an event; it binds the
owner-aware production and retrieval policy that makes an accepted event
intelligible to ReAct.
This distinction matters when building a rich agent. Adding a
mail search tool answers "what may the model ask mail to do?"
Adding a mail event source answers "how does an inbound mail
occurrence become a timeline contribution, and how can its referenced body
or attachment be pulled?"
11 Three context realms meet in one turn
A configured ReAct agent does not rely on one undifferentiated memory
bucket. mem contributes a bounded curated hotset to ANNOUNCE
and remains searchable as a named service. conv is the durable
temporal record: prompts, answers, summaries, tool contributions, events,
and artifact refs — the agent and chat UI use the same conversation-search
backend. cnv is a thematic board of proxies, attached or pulled
when the current task needs that assembled context.
These realms are runtime inputs, not parts of the ephemeral agent object. Read The Three Memory Realms for their ownership contracts, and Your Conversations Are Now Searchable — By You for the shared human/agent retrieval surface.
The workspace follows the same explicit-reference discipline. ANNOUNCE
shows a live map of current and historical refs; react.pull
materializes exact historical or owner-controlled content when needed. See
What
the agent always sees: a live map of its workspace.
12 How the chat surface connects
The chat component does not own a second agent definition.
EngineConfig.agentId identifies which configured agent the
conversation targets; agent_capabilities reads the live
administrator inventory, current conversation selection, and consent
coverage; the composer picker and expanded modal share one local draft, and
Save changes writes one conversation-bound patch; the
standalone capabilities widget, when opened without a conversation id, edits
defaults for future conversations; each turn independently resolves and
applies the durable selection, including its cache policy — no in-memory
session mutation is required.
The UI is therefore a projection and editor of the same runtime contract, not a parallel source of truth.
13 The payoff
A production ReAct agent is not "a prompt plus tools." It is a per-turn composition of durable state and independently owned policy: app code chooses the workflow; config declares the maximum inventory and runtime behavior; the user deliberately narrows one conversation; tool traits govern multi-action causality and execution timing; consent is demanded at the operation that needs it; event sources bring external change into the shared timeline; subagents are installed only when both administrator and user allow them; cache cost is visible and governed; the timeline, workspace, and memory survive the ephemeral runtime object.
That separation is what lets the next turn be assembled fresh without becoming stateless, configurable without becoming ambiguous, and powerful without moving authority into the model.