Named services are the interface that lets an agent enter a new app realm without that agent or its host learning the app's private domain rules.

An app owns a namespace such as task:, mem:, cnv:, repo:, or a future domain. It registers a named-service provider. A consuming app configures which provider operations are allowed for its agent, UI, scene, pinboard, jobs, or external callers. ReAct then gets a small set of generic tools and policies that can explore, materialize, render, and mutate objects in that realm.

The important boundary is this: the generic agent runtime owns react.pull / react.read, the named_services.* tool calls, timeline storage, strategy governance, and the configured access policy. The provider app owns the object_ref grammar, object schema, search spaces, object capabilities/actions, mutation rules, bytes/materialization, block production, and render and compaction projection.

The agent sees one universal interface. The provider remains the owner of meaning.

WHO OWNS WHAT — THE OWNERSHIP BOUNDARY Generic agent runtime owns one interface — no domain rules inside react.pull / react.read materialize & examine owner objects named_services.* tool calls the model-callable surface timeline storage where produced blocks live strategy governance exploration / exploitation ordering configured access policy which operations are allowed, per surface | Provider app owns the meaning of the realm object_ref grammar task:issue:ticket_123 object schema fields · enums · payloads search spaces scopes · filters · scoring capabilities / actions open · preview · download mutation upsert · delete · validate bytes / materialization object.get(response_mode=stream) block production block.produce render / compaction block.render · projection The agent sees one interface; the provider owns meaning.
The agent sees one interface; the provider owns meaning.
THE MAIN INTERFACE — MODEL TO PROVIDER ReAct model + tool loop named_services.* tools explore / exploit (model-callable) react.pull / react.read materialize / examine Consumer app config surfaces.as_consumer discovery / transport NamedServiceProvider owner namespace fan-out to provider operations read object.search object.schema object.get write object.upsert object.action host_file render infra-called, not model tools block.produce block.render event.resolve One generic path down — ReAct to provider — then the provider fans out into read · write · render operations it owns.
One generic path from the model down to the provider — which then fans out into the read, write, and render operations it owns.

Why this exists

Without named services, every agent-facing integration becomes bespoke:

task tools know task schemas;
canvas tools know canvas cards;
memory tools know memory records;
chat chips know how to open each foreign object;
scene code parses mem: or task: prefixes;
ReAct gets custom read/render logic for every new subsystem.

That does not scale. It also breaks provenance: once an object is copied into a chat message, pinboard card, or generated file, the system can lose which realm owned the evidence.

Named services solve this by making the owner realm explicit and reusable. For an object_ref = task:issue:ticket_123, the task provider owns the schema, search, preview, open action, mutation, and model-visible rendering — while chat, scene, pinboard, and ReAct own where the object is shown, which operations are allowed, and how the returned provider result is routed.

object_ref = task:issue:ticket_123

task provider owns:
  schema
  search
  preview
  open action
  mutation
  model-visible rendering

chat, scene, pinboard, ReAct own:
  where the object is shown
  which operations are allowed
  how the returned provider result is routed

The object keeps the same object_ref as it moves between chat, scene, pinboard, ReAct, MCP/API calls, Data Bus jobs, and cron outputs.

The four agent interfaces

Named services touch an agent in four different places. They should not be collapsed into one "tool" concept.

FOUR PLACES NAMED SERVICES TOUCH THE AGENT 1 Model-callable tools the model decides to call named_services.search_objects named_services.object_schema named_services.upsert_object explore the realm, read schema, mutate 2 Materialization pull → read bring bytes into the workspace react.pull(object_ref) object.get(stream) react.read(fi:) → owner block.produce the exact-content path 3 Prompt-render policies what the model actually sees timeline_projection announce_production block.render compaction_projection provider owns visible form 4 UI action path outside the model loop object.resolve object.action(open) ui_event.target_surface scene / pinboard / chat route the result The model-callable tools are only one of four surfaces — the full interface is all four together.
The model-callable tools are only one of four surfaces — the full interface is all four together.

1. Model-callable tools. named_services.provider_about, named_services.search_objects, named_services.object_schema, named_services.upsert_object, and so on.

2. Materialization path. react.pull(object_ref) calls the provider object.get(response_mode=stream), which writes a fi: workspace artifact; react.read(fi:) then dispatches to the owner block.produce.

3. Prompt rendering policies. timeline_projection, announce_production, compaction_projection, and the provider block.render hook.

4. UI action path. object.resolve and object.action(open/download/preview) resolve a ui_event.target_surface, which the scene, pinboard, or chat route to a mounted component.

The model-callable named-service tools are only one surface. The richer interface is the combination of tools, pull/read, rendering policies, and UI actions.

Note that block.produce, block.render, event.resolve, and object.get(response_mode=stream) are provider operations the ReAct infrastructure calls during materialization and rendering — they are not model-callable named_services.* tools.

Provider registration

A provider app registers the namespace it owns and the operations it supports. The registration is not just a Python class. It is the public contract for how the realm can be searched, read, displayed, and changed.

@named_service_provider(
    provider_id="task.issue",
    namespace="task",
    operations={
        "provider.about": {"transports": ["bundle_registry"]},
        "object.search": {"transports": ["bundle_registry"]},
        "object.schema": {"transports": ["bundle_registry"]},
        "object.get": {"transports": ["bundle_registry"]},
        "object.resolve": {"transports": ["bundle_registry"]},
        "object.action": {"transports": ["bundle_registry"]},
        "object.upsert": {"transports": ["bundle_registry"]},
        "object.delete": {"transports": ["bundle_registry"]},
        "block.produce": {"transports": ["bundle_registry"]},
        "block.render": {"transports": ["bundle_registry"]},
    },
    search_scopes=[
        {
            "namespace": "task:issue",
            "label": "task issues",
            "object_kind": "task:issue",
        },
        {
            "namespace": "task:attachment",
            "label": "task attachments",
            "object_kind": "task:attachment",
        },
    ],
)
class TaskIssueProvider(NamedServiceProvider):
    ...

At app startup, the app exposes a registry:

def named_services(self):
    registry = NamedServiceRegistry()
    registry.register(TaskIssueProvider(...))
    return registry

The runtime records the provider in Named Service Discovery. Consumer apps do not need to hardcode the provider location. They configure access to the namespace and let discovery pick the registered provider for the current tenant/project context.

REGISTRATION & DISCOVERY — PROVIDER REGISTERS, CONSUMER RESOLVES PROVIDER APP — ON LOAD build named_services() provider registry register provider id · namespace · operations · search scopes writes record Named-Service Discovery stores the provider record task.issue → provider · ops · scopes CONSUMER APP — CALL namespace task discovery resolves finds the task.issue provider namespace → provider record NamedServiceClient calls object.search · object.upsert · … on the resolved provider reads record The provider writes its record into discovery; the consumer resolves a namespace through the same node — no direct coupling between the two apps.
On load the provider builds its named_services() registry and lands a record in the discovery table; a consumer call on the namespace is matched to that provider and a NamedServiceClient invokes the owned operations.

Consumer registration for agents

The consumer app decides which namespace operations its agent may use. This is configured under surfaces.as_consumer, because the agent is one consumer surface among others.

surfaces:
  as_consumer:
    default_agent: main
    agents:
      main:
        tools:
          - id: task_service
            kind: named_service
            alias: named_services
            namespaces:
              task:
                allowed:
                  - provider.about
                  - object.search
                  - object.schema
                  - object.get
                  - object.host_file
                  - object.upsert
                  - object.delete
                tool_traits:
                  provider_about:
                    strategy: [exploration]
                  search_objects:
                    strategy: [exploration]
                  object_schema:
                    strategy: [exploration]
                  get_object:
                    strategy: [exploration]
                  host_file:
                    strategy: [exploitation]
                  upsert_object:
                    strategy: [exploitation]
                  delete_object:
                    strategy: [exploitation]

        event_sources:
          - kind: named_service
            namespace: task
            enabled: true
            discovery:
              mode: service_discovery
            policies:
              pull:
                mode: provider
                operation: object.get
              block_production:
                mode: provider
                operation: block.produce

    ui:
      canvas:
        resolvers:
          - kind: named_service
            namespace: task
            enabled: true
            discovery:
              mode: service_discovery
            allowed: [object.resolve, object.action]

There are three separate permissions here. agents.<agent>.tools exposes the model-callable named_services.* operations. agents.<agent>.event_sources enables the ReAct pull/read/render path for owner objects. ui.canvas.resolvers enables the scene / chat / pinboard object.resolve and object.action.

agents.<agent>.tools — model-callable named_services.* operations
agents.<agent>.event_sources — ReAct pull/read/render path for owner objects
ui.canvas.resolvers — scene/chat/pinboard object.resolve and object.action

A namespace can be pullable by react.pull without exposing named_services.get_object to the model. A namespace can support UI open actions without exposing object_action as a model-callable tool.

Canonical named-service tools

The current generic ReAct tool adapter exposes these model-callable tools when the consumer config allows the corresponding provider operations.

Canonical named-service tools — model-callable surface mapped to provider operations.
Tool Provider operation Usual strategy Purpose
named_services.provider_about provider.about exploration Understand what the provider owns and which objects/scopes it exposes.
named_services.list_objects object.list exploration Page through a bounded collection when listing is meaningful.
named_services.search_objects object.search exploration Search a base namespace or provider-declared search scope. Emits generic search-result context rows.
named_services.get_object object.get exploration Read one bounded object envelope. For exact/large bytes, prefer react.pull then react.read.
named_services.object_schema object.schema exploration Ask the provider for fields, object kinds, filters, upsert/delete payloads, and usage guidance.
named_services.host_file object.host_file exploitation by default Move a ReAct artifact/file into provider-owned storage and receive a provider-owned ref.
named_services.upsert_object object.upsert exploitation by default Create or update a provider object according to provider schema.
named_services.delete_object object.delete exploitation Delete, archive, retire, or suggest deletion according to provider policy.
named_services.object_action object.action action-dependent Run bounded provider actions such as preview/open/download/describe. Usually used by UI resolvers, not exposed broadly to the model.

The strategy column is not hardcoded truth. It is deployment policy. For example, memory recording may be configured as neutral if the product treats it as side-memory rather than an answer-changing write. A provider action that only describes an object may be exploration, while a provider action that sends mail or changes status is exploitation.

The agent sees traits in the tool catalog:

Scope:
    - namespaces applicable: task, mem, cnv
    - strategy: exploitation (default)
    - strategy overrides by namespace:
        - mem: neutral

That trait is used by online ReAct governance, which classifies every tool against four trait values — exploration, exploitation, neutral, and unknown. In the same model round, exploration -> exploitation is denied because the exploitation would depend on a result that has not yet been shown to the model. exploitation -> exploration is allowed for staged work.

Searchable namespaces and search scopes

A namespace is not necessarily one flat object space. A provider can declare search scopes:

task
  task:issue
  task:attachment

sensor
  sensor:temperature
  sensor:humidity:aggr

cnv
  cnv

Search scopes are provider registration metadata. They let the tool catalog show the model where search is meaningful without making a live provider.about call first.

search_scopes:
  - namespace: task:issue
    label: task issues
    object_kind: task:issue
    filters:
      status:
        type: string
        enum: [open, in_progress, blocked, resolved]

The consumer authorizes the base namespace:

namespaces:
  task:
    allowed: [object.search]

The model can then call a scoped namespace:

{
  "namespace": "task:issue",
  "query": "membership cancellation",
  "filters": "{\"status\":\"open\"}"
}

The provider owns what the scope means. Search may be lexical, semantic, hybrid, RRF-ranked, memory-weighted, or something domain-specific. The provider also owns filter names and relevance tuning.

SEARCH SCOPES — DECLARED BY THE PROVIDER, AUTHORIZED BY THE CONSUMER Provider namespace task declares search scopes: task:issue label · filters · object_kind task:attachment label · filters · object_kind Consumer config authorizes the base op: object.search namespaces: task: allowed: [object.search] Model may call a scoped namespace: task:issue query: "membership cancellation" filters: {status: open} Scopes appear in the tool catalog without a live provider.about call. They are provider registration metadata — the catalog already knows where search is meaningful. The provider owns what the scope means — lexical, semantic, hybrid, RRF, memory-weighted — plus filter names and relevance tuning.
Scopes appear in the tool catalog without a live provider.about call.

Object schema before mutation

The agent should not infer object bodies from visual cards or old examples. It asks the provider:

named_services.object_schema(namespace="task", object_kind="task:issue")

The schema can describe:

object kinds;
create/update fields;
strict enums;
open vocabularies with known/suggested values;
filters for search/list;
mutation payload examples;
optimistic concurrency requirements;
required object_ref shape;
provider-specific delete/archive semantics.

Then the model calls mutation with provider-schema JSON:

{
  "namespace": "task",
  "object_ref": "task:issue:ticket_123",
  "base_revision": "7",
  "object_json": "{\"status\":\"blocked\",\"comment\":\"Waiting for contract confirmation.\"}"
}

Payload fields are provider-owned. They do not control named-service routing. Routing is controlled by the tool name, configured namespace, operation, and provider discovery.

Pull, read, and owner block production

named_services.get_object is not the main exact-content path. The ReAct object materialization path is:

react.pull(paths=["mem:record:mem_123"])
  -> namespace rehoster
  -> provider object.get(response_mode=stream)
  -> local artifact:
       logical_path  = fi:turn_1.files/mem_123.json
       physical_path = turn_1/files/mem_123.json
       object_ref    = mem:record:mem_123
       scope         = files or snapshots

react.read(paths=["fi:turn_1.files/mem_123.json"])
  -> generic read target with object_ref
  -> owner event source resolution
  -> provider block.produce
  -> model-visible owner blocks

scope is workspace materialization metadata. It is not the owner namespace. The owner identity remains object_ref.

PULL / READ — THE EXACT-CONTENT MATERIALIZATION PATH react.pull (object_ref) provider object.get response_mode=stream local fi: artifact logical_path = fi:turn_1.files/mem_123.json object_ref = mem:record:mem_123 (PRESERVED) scope = files | snapshots scope = workspace materialization, not the namespace react.read (fi:turn_1.files/mem_123.json) → object_ref carried owner block.produce read target → timeline blocks model-visible owner blocks get_object ≠ pull/read get_object is a bounded envelope; pull/read is the exact-bytes path. react.pull materializes exact bytes; react.read dispatches to the owner to produce model-visible blocks — the object_ref is carried the whole way.
react.pull materializes bytes with object_ref preserved; react.read hands them to the owner's block.produce.

For a volatile object, the owner may intentionally produce only a compact timeline fact and put the rich current view in ANNOUNCE. Canvas is the reference example:

react.pull(paths=["cnv:main"])
  -> fi:turn_1.snapshots/cnv/main.json

react.read(paths=["fi:turn_1.snapshots/cnv/main.json"])
  -> [CANVAS TOOL RESULT] timeline fact
  -> [CANVAS BOARD] in ANNOUNCE for N render rounds

The compact fact tells the model how to refresh:

announce_effect: board projection refreshed in ANNOUNCE for 3 render rounds
refresh_rule: use react.pull(paths=['cnv:main']) and react.read on the returned fi: path if you need an updated or prolonged board view

This text belongs to the canvas owner policy. Generic ReAct does not know that cnv: means canvas or that the object is volatile.

For stats_only reads, the owner can emit top-level original_object_stats. ReAct copies that into the stats response without interpreting domain fields.

Rendering policies: what the model sees

Named-service integration is not complete unless the provider controls model-visible representation. There are several policy surfaces.

block.produce
  called during react.read
  turns one object/read target into bounded timeline blocks

timeline_projection
  render-time mutation of already-produced timeline blocks
  good for compact facts instead of raw JSON

announce_production
  non-durable prompt-tail context
  good for current focused state, board maps, active forms

block.render
  provider hook called during prompt rendering
  can patch provider-owned visible blocks

compaction_projection
  representation for summarizer/compaction input
  should preserve stable refs and facts, not prompt-only ANNOUNCE text
RENDERING POLICY SURFACES — EACH ANSWERS ONE QUESTION WHAT TO SEE NOW block.produce called during react.read "What becomes a timeline block?" timeline_projection render-time block mutation "How does it read compactly?" announce_production non-durable prompt tail "What focused state to surface?" block.render prompt-render patch hook "What blocks to patch now?" These four shape the live, visible view — what the model needs in order to act this round. WHAT SURVIVES COMPACTION compaction_ projection input for the summarizer "Which stable refs & facts survive?" Seam exists today; some preservation still hardcoded. Normal render asks "what should the model see now to act?" — compaction render asks "what stable facts and refs should survive when this block leaves the visible window?"
Four policies shape what the model sees now; compaction_projection shapes what survives compaction.

The compaction phase seam exists in the code today: the compaction_projection hook is already wired so providers can shape summarizer input, though some preservation is still hardcoded rather than provider-owned. The direction is that providers fully own compaction rendering. A task provider can decide which issue fields survive summarization. A memory provider can decide which salience/freshness fields matter. A canvas provider can preserve cnv:main@52 and selected card ids while not treating the rendered ANNOUNCE board map as authoritative state.

Compaction projection answers a different question than normal prompt rendering:

normal render:
  What should the model see right now to act?

compaction render:
  What stable facts and refs should survive when this block leaves the visible
  window?

Provider policies should therefore keep recovery anchors explicit:

object_ref: task:issue:ticket_123
revision: 7
status: blocked
recover_with: react.pull(paths=["task:issue:ticket_123"])

UI actions and pinboard

The same provider contract is used outside the model loop. A canvas card stores object_ref = task:issue:ticket_123. When the user clicks open, canvas/scene calls object.action(open, object_ref). The task provider returns a ui_event.target_surface = task_tracker.issue_editor with an action and payload, and the scene routes that target_surface to the mounted component alias.

canvas card stores:
  object_ref = task:issue:ticket_123

user clicks open:
  canvas/scene calls object.action(open, object_ref)

task provider returns:
  ui_event.target_surface = task_tracker.issue_editor
  ui_event.action = open
  ui_event.payload = { issue_id: ... }

scene routes:
  target_surface -> mounted component alias
UI ACTION PATH — CARD TO MOUNTED COMPONENT canvas card stores object_ref: task:issue:ticket_123 object.action (open, object_ref) scene passes full ref provider resolves ui_event.target_surface = task_tracker.issue_editor scene surface command target_surface → alias mounted component The scene passes the full object_ref; the provider decides the action. Canvas and scene never decide that task: means "issue editor" — namespace presentation gives only colors, icons, labels.
The scene passes the full object_ref; the provider decides the action.

Canvas and scene do not decide that task: means issue editor. They pass the full object_ref to the provider resolver. Namespace presentation config provides colors, icons, and labels. Provider resolver results provide capabilities and actions.

Pinboard stores proxy cards. The card layout belongs to canvas. The referenced object still belongs to its provider realm.

Pinboard card
  layout: x/y/w/h, comments, description, selected state
  object_ref: mem:record:mem_123
        |
        v
memory provider
  preview/open/read/search/mutate memory record

How to add a new agent realm

For a new app realm, implement this checklist.

Provider app:

  1. Choose canonical object_ref grammar.
  2. Define object kinds and namespace presentation config.
  3. Implement NamedServiceProvider.
  4. Expose named_services() registry.
  5. Register provider operations and search scopes.
  6. Implement provider.about and object.schema.
  7. Implement search/list/get as needed.
  8. Implement object.get(response_mode=stream) if ReAct should pull objects.
  9. Implement block.produce for react.read.
  10. Implement block.render if provider-owned blocks need prompt-time patches.
  11. Implement compaction_projection policy or provider compaction renderer when the realm needs custom summary/recovery shape.
  12. Implement object.resolve and object.action for UI surfaces.
  13. Implement mutation operations only with provider-side schema validation.

Consumer app:

  1. Add namespace under surfaces.as_consumer.
  2. Allow only the operations each agent/surface needs.
  3. Configure tool_traits for ReAct governance.
  4. Configure named-service event source/pull/block-production policy.
  5. Configure UI resolvers for canvas/chat/pinboard/scene.
  6. Add scene surface command contracts for provider-returned target_surface values.
  7. Test discovery logs, tool catalog, search scopes, object actions, pull/read, and compaction behavior.

Useful diagnostics

When the agent cannot see a provider tool:

check surfaces.as_consumer.agents.<agent>.tools
check namespace allowed operations
check named_services tool catalog
check strategy traits rendered in tool scope

When a provider cannot be found:

Named-service discovery provider scan
Named-service endpoint resolution
selected_provider: <none>
reason: no matching provider

When search scopes are missing:

[named_services.tools.search_scopes]
registry_keys=...
discovery_entries=...
entry_scopes=...
result_counts=...

When a pulled object reads as a generic file:

react.read.owner_projection
status=no_event_source | no_blocks | policy_error | produced

When a UI card stays "resolving":

object.resolve / object.action logs
provider ui_event present?
namespace presentation config present?
surfaceCommandContracts target_surface mapped?

When a canvas board appears twice in ANNOUNCE:

check whether chat.canvas.state and canvas.read both refer to same cnv:<board>
owner announce policy should consolidate by object identity
KDCube Deep · 24.06.2026