KDCube
← Engineering
KDCube Engineering · Experience

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.

6 July 2026Engineering13 minExperienceEngine Room
react agentagent constructioncapabilitiestool traitsANNOUNCEcache policyconsentsubagents

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).

THE DISTINCTION

"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:

AuthorityDecidesWhen it acts
App codeHow the workflow composes and runs ReAct.Construction and execution.
App configWhich models, tools, skills, services, event sources, and subagents may exist.Inventory resolution.
User selectionWhich offered capabilities this conversation should use, plus one allowed model pick.Explicit Save, then turn start under cache policy.
Runtime fencesWhether this concrete call has the required role, claim, consent, budget, and valid parameters.At operation attempt.
THE INVARIANT

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:

REACT CONSTRUCTION · NEW RUNTIME OBJECT, DURABLE INPUTSapp codebuild_reactadmin configthe ceilingconversation choicethe user’s narrowingturn statetimeline + context1 · resolve tool inventoryPython · MCP · named services · allowed calls · traits · claim policies2 · resolve skill inventoryapp-local skill root · visibility rules3 · apply conversation selectionnarrow inventory · model overlay · subagent toggle · cache policy4 · refresh consent transitionscheck only earlier demands · announce newly reactivated tools5 · compose stable teachingprotocol · app instructions · catalogs · skills · namespace roster6 · build_react(...)tools + traits · event-source specs · optional subagent spawneruser choice never widensbookkeeping, not a fence7 · react.run(...) — one ReAct turnrender instruction + timeline + ANNOUNCE;stream decisions · fence and dispatch · fold results · persistthe operation fencerole · claim · consent · economicsSELECTION SHAPES THE CATALOG · RUNTIME FENCES AUTHORIZE THE OPERATIONBUILT FRESH EVERY TURN · THE DURABLE STATE IS THE INPUT
Fig. 1 — selection shapes the catalog; runtime fences authorize the operation.

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.rootsMAP
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:

bundles.yamlYAML
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:

tool.traitsVOCABULARY
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.

consent.flowJOURNEY
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:

TWO MODEL-INPUT HOMES · OPPOSITE LIFECYCLESSYSTEM INSTRUCTIONstable · cached prefixdurable teaching — what the agent should keep knowingprotocol · house rulestool catalog + traitsskill gallerynamed-service grammar · rosterapp additional_instructionschanges only when the capability / teaching contract changesCOLD ONCE WHEN IT CHANGES · THEN WARM AGAINANNOUNCEper round · uncached tailsituational truth — what is true right now[BUDGET] · [RUNTIME LIMITS] · [CACHE][USER MEMORY HOTSET] · [WORKSPACE][DELEGATION] · live event foldspreviously demanded tool is now availablevolatile by design — no stable-prefix rewriteMAY CHANGE EVERY DECISION ROUNDThe placement test: durable teaching, or the current situation?MISSING CONSENT RETURNS AT THE ATTEMPT · ANNOUNCE REPORTS TRANSITIONS, NOT PREDICTIONS
Fig. 2 — durable teaching in the cached prefix; situational truth in the uncached tail.
THE PLACEMENT TEST

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.

selection.journeyJOURNEY
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:

selection.recordJSON
{{
  "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 choiceRuntime effect
Tool group or individual toolRemoved from the turn's resolved tool config. Locked system groups remain on.
MCP server or MCP toolRemoved from that connection's effective set.
Named-service realmRemoved from both roster and dispatch.
Realm operationThat generic operation is rejected at named-service dispatch.
Named actionOnly that domain action is rejected; sibling actions remain available.
SkillRemoved from the skill gallery and consumers.
SubagentsSpawner is not installed; react.delegate and its guidance are absent.
ModelValidated 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.

ADMINISTRATOR CEILING · CONVERSATION CHOICE · EFFECTIVE TURNadministrator offersCONFIGURED INVENTORYPython tools · MCP serversservice realms · operations · actionsskills · helper agentssupported modelssystem tool groups locked onthe maximum, never the user’s final setuser tunes this conversationLOCAL DRAFT → SAVE CHANGESdeny web_fetchdeny mail action: senddeny task operation: deletedisable one skill + subagentspick Haiku 4.5remove-only + one allowed model pick=the turn runs withEFFECTIVE CAPABILITIESweb without web_fetchmail without sendtask without object.deleteskill hidden · no react.delegatestrong decision role: Haiku 4.5selection is resolved again each turnEFFECTIVE = CONFIGURED − DISABLED · CLAMP ON SAVE · NARROW AGAIN AT TURN STARTNEVER WIDER THAN GRANTED · STORE FAILURE FALLS BACK TO THE CONFIGURED INVENTORY, FENCES STILL HOLD
Fig. 3 — administrator grant ∩ conversation tune = the effective turn.

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.

SELECTION CHANGES · CACHE CONSEQUENCE · USER CONTROLmodel changedprovider caches are model-specificcold on the selected modelno reusable prompt for that model yetthen warm normally on that modelcapability changedtool · skill · MCP · realm · subagentwhole prompt cold oncechanged system block precedes historyone attributable cold turn: [CACHE] + metaANNOUNCE state changedbudget · workspace · hotset · progressstable prefix unchangedvolatile truth stays in the uncached tailexisting prefix remains reusableTHE PAYING USER HOLDS THE POLICYacceptconfirm · defaultdefer_colddefer_conversationUNDER CONFIRM: APPLY NOW · NEXT CONVERSATION · WHEN ALREADY COLD · DEFERRED CHANGES PROMOTE ON TRIGGER
Fig. 4 — each switch is named, priced, and governed; none is silent.

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:

two.directionsMAP
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.

· Related articles

· Documentation on GitHub

KDCube Engineering
№ 2026-07-06 · updated 2026-07-16 · kdcube.tech