Tools & the tool boundary
A tool is something an agent can do rather than say. Every tool call crosses from the untrusted loop into the trusted backend, which is the single most important boundary in Kaizen's design.
The closed built-in registry
Kaizen-owned tools are a closed set. The registry names every built-in tool the loop may register; adding one is a deliberate change, because each new implementation expands the loop's trust surface. The generic Kamiwaza broker described below is one reviewed adapter in that set. Its generated operation descriptors are untrusted live metadata, not new Python implementations.
| Tool | Kind | What it does |
|---|---|---|
search | read | Retrieval over the authorized corpus |
conversation_history_search | read | Authorized prior-work discovery |
kamiwaza_connector_catalog | read | Request-scoped connector inventory and readiness |
kamiwaza_connector_rag | read | Federated live folder, file, and evidence discovery |
kamiwaza_mcp_catalog | read | Request-scoped inventory of MCP tools authorized for this run |
list_available_agents | read | The member-safe agent roster |
search_about | read | Search the exact About Markdown bundled with the product |
query_document | read | Read one of the member's documents |
request_document | read | Re-authorize a retrieved document and add its exact file to this turn's sandbox projection |
visualize_read_me | read | Presentation guidelines loader |
inspect_tasks | read | Owner-scoped Task, Goal, progress, and run inspection |
manage_tasks | write | Owner-scoped Task mutation, one-shot background work, and bounded Agent loops |
run_code | write | Execute code in the sandbox |
open_browser_session | write | Lease a shared browser worker and open one HTTPS page |
read_browser_page | read | Read the shared page as a bounded accessibility tree |
act_on_browser_page | sensitive | Click, type, scroll, or press a key after member confirmation |
read_workspace | write | Read full or ranged numbered UTF-8 text under a trusted four-hex Hashline snapshot |
edit_workspace | write | Apply a guarded OMP-compatible multi-file Hashline transaction |
create_artifact / update_artifact | write | Single-call full-content writes to the member's versioned artifact store |
read_artifact | read | Read up to 400 numbered lines from the latest owner- or active-room-authorized artifact |
edit_artifact | write | Atomically apply version-bound Hashline ranges while streaming a provisional pane preview |
begin_artifact / append_artifact / finalize_artifact / discard_artifact | write | Fenced staged creation for substantial artifacts |
show_widget | write | Generative-UI artifact |
inspect_library | read | Identity-only listing of one Library surface: documents, artifacts, collections, prompts, projects |
manage_library | write | File an existing Library item into a Collection or link it into a Project |
invoke_agent | read | Delegate to a sub-agent without granting new authority |
| Studio readback tools | read | Owner-scoped proposal indexes, exact draft views, bounded package-file reads |
| Studio mutation tools | write | Closed field patches, package-file preview/apply, dependency resume, and confirmed submission |
Registration is conditional. run_code is built only when SANDBOX_ENABLED
and offered only to an agent whose grant explicitly names it. Studio tools are
registered per turn, only when Agent Studio is enabled and only for the
source-defined authoring agents. The former ticket-action tools are retired and
are absent from the registry, capability catalog, and built-in package.
inspect_library and manage_library are the member's own Library, exposed to
an agent. They follow the inspect_tasks / manage_tasks split: discovery is a
read that returns identity only — id, name, and one short chooser hint per
item — so an agent can turn a surface the member named into the id another tool
needs, while content stays with the tools that own it (search,
query_document, read_artifact). Authoring stays with its own tools too:
create_artifact / update_artifact for artifacts, manage_tasks for tasks.
Agents and tasks are not re-listed here, because list_available_agents and
inspect_tasks already carry those inventories.
Each surface answers to its own feature flag, and a surface a deployment does not expose is indistinguishable from one that does not exist. Discovery reads through the same repository or service the member's own screen uses, so a listing can never contain an item the member could not open themselves.
manage_library organizes and nothing else — no create, rename, delete, or
reshare, and no path that copies content out of a connector. It has exactly two
destinations, and each one routes to the member-side write that already owns it:
- Collection — the same implementation as
POST /api/collections/{collection_id}/documents, so the ownership check, the ACL re-resolution of the item, the idempotent membership upsert and themember.collection.doc_addedaudit row cannot drift between a member's click and an agent's call. A collection references documents, so an artifact is fileable exactly when it is one of the member's own documents opened as a canvas; an artifact that is not gets a non-retryable refusal saying so, rather than a membership row the collection could never render. - Project —
ProjectLibraryItemService.link, the same non-owning pointer the Project UI creates, which re-resolves the item as that writer and refuses a composition cycle.
Every id the model supplies is a selector. The destination is re-read and re-checked for write authority inside the write, and the item is re-resolved through the same visibility predicates retrieval uses. A destination owned by somebody else, an item the caller's ACL does not admit, and an id that names nothing are one indistinguishable refusal with no partial write; a kind the destination cannot hold keeps its own actionable wording, because that is a statement about shape rather than about what exists.
Browser-session creation is one operation, not an invitation to probe around a failed prerequisite. The tool preserves the bounded transport or capacity reason and marks the requested open as non-retryable and non-substitutable. The runtime gives the model one response step to explain that failure. If it tries another browser or substitute tool instead, the shared repair gate blocks that call and ends the turn before the general tool budget is spent. A successful open carries no repair directive.
read_workspace and edit_workspace are paired with run_code and use the
same actor-and-conversation workspace lease. A read normalizes UTF-8 BOM and
line endings, records the full file in an owner/workspace-scoped trusted
snapshot store, records only the lines actually displayed, and returns an
OMP-compatible [PATH#TAG] header. The four-hex tag is an index into retained
full text, never a credential or standalone authorization value.
An edit accepts the complete Hashline grammar: ranged and gap PUT, syntax
blocks, CUT, named cross-call registers, REM, MV, and multiple file
sections. The trusted helper parses and applies against in-memory files while
the Python worker retains path authority. Every source, recovered path, and
destination is revalidated beneath the workspace; symlinks are refused. The
worker preflights all sections, stages writes, rolls the entire batch back on a
commit failure, limits each text file to 2 MiB, and returns a bounded unified
diff plus block resolutions and warnings.
When live content differs from the retained snapshot, unchanged anchors may be remapped onto one unambiguous contiguous region. Changed, duplicate, unseen, or ambiguous anchors fail without writing. Named registers and up to four retained versions per path live in trusted state outside the untrusted workspace so worker release and replica changes cannot widen or lose authority.
read_artifact and edit_artifact apply the same anchor principle to the
versioned artifact surface without treating artifacts as sandbox files. Focused
artifact context renders [artifact#TAG] plus numbered lines. When a target is
beyond the bounded prompt context, the read tool returns an owner-scoped window
of up to 400 lines, the latest immutable version, total line count, content
digest, and matching tag.
One edit may contain up to 32 non-overlapping ranged PUT and CUT hunks. All
line coordinates refer to the displayed immutable base and apply bottom-up as
one transaction. The tool resolves the supplied artifact again under the acting
member, requires its exact version and content-derived tag, then enters the
ordinary write guard. Bounds, overlap checks, artifact-kind validators,
prompt-chain rules, and the store's locked optimistic version check all run
before one immutable version is appended. An active-room foreign version may
produce one completed collaborator-owned remix; it never mutates the creator's
root.
The model's patch bytes are transient presentation input. The pane projects them over the fetched immutable base, labels the active source range, and shows the before and streamed replacement. In a shared room the transient collaboration relay distributes the same accumulated patch snapshot to every current participant and to reconnecting participants; it does not create a private initiator-only preview.
Markdown and the safe HTML, SVG, and widget frames update live. React and Mermaid candidates validate in an isolated hidden frame; the pane retains the last valid render until the exact candidate succeeds. Token-level replacement text is excluded from the screen-reader live region. No partial patch is durable: a failed, stopped, malformed, or stale edit restores the committed version for every viewer, while a bounded informational strip reports whether the edit applied.
Both workspace tools deliberately use the write capability tier. A
read-only/search agent therefore never receives a private sandbox file surface
without also being granted the sandbox execution capability it belongs to.
run_code remains ephemeral by default. A Python or Node call may request a
persistent kernel for iterative analysis. Python exposes tools.available and
tools.call(name, arguments); Node exposes await tools.call(...) and a
JSON-serializable state object. Callback names come only from read tools
advertised by the active provider snapshot. The trusted worker controller keeps
the run capability and descriptor digests out of the code process, and the
backend repeats snapshot, descriptor, guardrail, and member checks before
executing a callback. Writes and recursive run_code callbacks are refused.
The run_code contract also states, in the tool's own description and in the
code parameter, that the turn's authorized files may already be on disk. The
worker sets KAIZEN_SHARED_RESOURCES_DIR to the read-only projection
directory and KAIZEN_SHARED_RESOURCES_MANIFEST to a JSON array whose entries
carry the resolved path along with the reference id, kind, version, content
digest, filename, media type, and size. Both are absent when the turn has no
authorized file. Because the manifest supplies the path, the same discovery
works from Python, Node, and Bash, and no agent has to guess at a filename the
server may have sanitized. Bytes never travel through tool arguments, prompts,
the manifest, stdout, or the audit record — only through the sealed files
themselves.
The description also carries short idioms for the document writers a file
request reaches — python-pptx, ReportLab, python-docx, openpyxl, and
Matplotlib — aimed at the calls models get wrong in practice: reading a colour
that was never set, calling a canvas method that does not exist, or using a
name that was never imported. A unit test executes those idioms against the
real libraries (pinned dev dependencies matching the sandbox bundle), so the
text cannot drift into advice that fails. A failed or
timed-out run's payload adds a repair_instruction naming the exception and
asking the model to fix the code and run it again, unless the member asked for
no retries; the model reads the payload, not the summary, so the instruction
lives there.
create_artifact's description keeps short answers in chat by default, but an
explicit request to create, make, build, or write a report, document, page,
diagram, or other artifact overrides that size heuristic: the agent gathers what
it needs and then creates the artifact even when the content is short.
request_document is registered only for a private turn that has both search
and run_code. It accepts an uploaded-document chunk identifier produced by
authorized retrieval, resolves the current visible parent document again, and
adds that exact version to execution-local context. It cannot resolve a title,
path, or arbitrary document identifier, and it is unavailable in a shared chat
where a private file would need an explicit audience-visible link.
search_about is a baseline read for every chat agent. The API image receives
a build-time copy of web/kaizen/src/docs/content, the same source directory Vite
bundles into the About screen. The tool returns bounded excerpts and /docs
paths; it has no database, network, identity argument, or mutation path.
The built-in set is asserted by a boundary test that walks the source. Adding a Kaizen implementation without updating that trip-wire fails the build. A new authenticated operation published by Kamiwaza does not edit the built-in set; it must pass the broker contract and the existing policy intersections before it can become callable.
manage_tasks keeps Goal and background lifecycle operations inside one
reviewed write boundary. run_in_background creates exactly one durable
attempt; cadence words such as every or interval select create_loop.
Both operations settle Core authority implicitly from the initiating member's
live verified bearer and return active work. update_goal invalidates prior
execution authority; answer_goal_input and steer_goal bind the current
accepted member input on the server. There is no model-visible goal, sleep,
wait, polling, consent, checkpoint, or process-control tool.
Studio proposal tools
Agent Builder and Package Creator share one proposal service but receive
different closed tool subsets. list_studio_proposals, get_agent_proposal,
and get_package_proposal expose only the initiating member's proposals.
Package file bodies are returned only for explicitly named safe paths, with a
bounded path count and aggregate response size.
patch_agent_proposal and patch_package_proposal accept an explicit current
version and an allowlisted changes object. Omitted fields are reconstructed
from the canonical draft and preserved. patch_package_files validates the
complete resulting local-package tree before applying an atomic update; its
preview returns paths, sizes, and digests rather than file bodies. Any byte
change clears earlier package-test evidence. Native Pi evidence also clears
when the ref, manifest, posture, or package format changes, because its receipt
binds both authored bytes and the reviewed contract.
A package proposal handoff is data, not authority. Agent Builder persists the
exact proposal ID, name, ref, and author as an unresolved dependency. Agent
submission and administrator approval both refuse while that record remains.
resume_agent_proposal converts it to an ordinary package grant only after the
same-owner proposal is approved and the exact package ref is active. Rejected,
foreign, changed, disabled, or missing packages never widen the grant.
Native Pi authoring profile
Package Creator can mark a local proposal as package_format=pi. That is a
closed host profile, not a user-controlled Pi version. The backend performs
pure checks over package identity, the pi-package keyword, host peer
dependencies, exact ordinary dependency pins, forbidden lifecycle scripts,
safe resource paths, reviewed skills, and declared tool schemas. It then sends
only a server-derived validation plan to the normal sandbox worker.
The sandbox worker is built from the same Pi lock as the Pi sidecar. Skill-only
packages use Pi's real skill discovery without fabricating a Node test.
Extension packages run node:test first and Pi's real extension loader second.
The loader never consumes the tree that ran those tests. The worker creates a
fresh exact tree from the server-supplied files and reinstalls dependencies
within the remaining overall deadline. A disposable child imports authored
extensions and returns a bounded observation; the trusted parent imports none
of that code, validates the observation, reads static resources, and owns the
inherited descriptor for the final result. Stdout cannot forge a validation
receipt. Before either loader receives a resource, the validator canonicalizes
its real path. Absolute paths, traversal, and symlinks that resolve outside the
workspace fail validation rather than reaching Pi's file APIs.
The worker reports tools, skills, prompts, themes, unsupported registrations,
diagnostics, whether tests ran, and the observed runtime version. The backend
normalizes and bounds that result, compares the discovered tool and skill
surface with the reviewed Kaizen manifest, and records a receipt containing both
the artifact hash and the non-file contract digest.
Kaizen supports tools, skills, prompts, and themes from this profile. Commands, flags, events, shortcuts, and custom renderers fail validation because Kaizen does not compose them. Neither missing sandbox capacity nor a loader error causes fallback to backend execution or the secretless package runner. Submission and administrator approval independently revalidate the current receipt, including under the general warning scan policy.
Two ceilings
An agent's reach is bounded twice, and both bounds must allow a call:
- The capability ceiling — the maximum set of tools this agent may ever use. A read-only agent can inspect tasks but cannot mutate them, no matter what a prompt asks for.
- The member's permissions — resolved at call time, not at turn start. An agent acting for you can only reach what you could reach yourself.
Neither ceiling is negotiable from inside a prompt, because neither is evaluated inside the model.
Prompt step attachments
A saved Prompt may explicitly attach a Tool or Connector to one authored step.
The editor inserts a structural {{tool:surface/id}} or
{{connector:opaque-id}} token; ordinary instructions that merely name a Tool
remain model content and grant nothing.
The authoring finder is intentionally broader than the selected Agent's usual defaults. On every validation and run, the Prompt resolver intersects each attachment with the running member, current administrator selection, live platform and Connector catalogs, active package pins, and the effective step Agent's capability ceiling. A missing, disabled, revoked, ambiguous, or over-ceiling attachment is a blocking validation error before any step runs.
An admitted attachment produces an execution-only copy of the step's
AgentSpec. It may add an exact package Tool, platform Tool, SDK adapter, or
Connector ID for that turn, but it never mutates the stored Agent or its package
grants. Imported package Tools are loaded again by active package UUID and exact
Tool name, capability-clamped, collision-checked, and sent through the same
secretless package runner and posture resolver as ordinary Agent grants.
Approvals, collaboration admission, audit, sandboxing, and callback-time member
authorization are unchanged.
Before the model sees the step, Kaizen replaces the structural token with a readable directive naming the exact authorized Tool or Connector. That improves selection and discovery; the directive is not the authority. The already resolved runtime catalog remains the only callable set.
Shared-room admission classes
A durable room does not receive every tool available to its initiating member. The shared pipeline classifies each tool before runtime wiring. Exact room resources and audience-intersection reads may enter the room. Bounded, secret-free metadata reads—such as the connector and MCP catalogs, the member-safe agent roster, and Studio proposal indexes—remain callable only as the initiating actor. Actor-bound writes also keep that actor identity through their guard and repository checks.
Package tools use the same closed policy without requiring a source-code name map for every package. Their admin-reviewed manifest may declare only an actor-bound read, actor-private read, actor-bound write, or unknown class. The trusted Python package wrapper carries that declaration; package code and model arguments cannot change it. A missing, malformed, or unknown declaration stays absent from a shared room. Package tools cannot claim conversation-resource or complete-audience authorization, because those classes require a dedicated adapter that proves the room scope. Dynamic platform operations remain absent under the same fail-closed rule unless their adapter has a separately reviewed room-safe projection.
Private and shared chats still use this one durable runtime. An actor-private adapter enters a turn only when the server-authored active participant roster contains exactly the initiating member; the callback repeats that roster-shape check before execution. Adding a collaborator therefore removes actor-private tools from later turns without relying on browser state.
Actor binding is not a general sharing grant. It admits only the named bounded projection for that call; it never exposes the actor's broader library, credentials, package files, or platform responses. The callback verifies the same initiating actor again before execution, and the room receives only the normal sanitized tool projection.
What happens on a tool call
When the runtime calls back:
- The authority bundle is re-checked — member, registered workload revision and role, run claim and fence, conversation, audience, agent version, resource descriptor, and request digest.
- The member's current permissions are re-resolved.
- The capability ceiling is checked.
- The resolved member approval mode is applied. A request that needs a decision is persisted with a digest of its arguments before any wait begins. Artifact argument fragments remain in the trusted backend and do not cross to the browser until that exact request is approved.
- For anything with an external effect, the write guard and the tool's own resource authorization are applied.
- Under delegated unattended authority, the protected action is additionally reserved as one exact Core effect. The registered resource contract — not tool or model input — classifies it, applies quota, selects a credential binding, and parks it for approval where required.
- The tool executes as the member — or, for delegated work, as the member-on-behalf-of workload pair once the resource guard consumes one sender-constrained effect capability before protected code runs.
- Citation and presentation lineage is captured.
- A bounded, member-safe projection is returned to the loop.
Step 9 matters as much as step 4. The loop receives a projection, not raw system output — which is why a tool result cannot smuggle credentials, ACL lists, or another member's content into the model's context.
show_chat_canvas is the closed, non-persistent presentation path for an
explicit inline-canvas request. It accepts bounded Markdown, creates no Library
row, performs no external action, and crosses the same typed presentation and
conversation-journal projection used by image and table tool results. The
browser renders its Markdown through the safe React renderer rather than
executing model-authored HTML. Requests to save, version, share, or download
content remain on the separate artifact tools and their write guard.
Files retained by run_code stay opaque native artifacts rather than being
reclassified as editable canvas artifacts. The member surface can request a
bounded preview of that exact stored file: the private path repeats the owner
ACL, while the collaborative path resolves the immutable exact-room resource
reference. Kaizen derives Office text locally with bounded archive and XML
limits; it never delegates preview authority or file bytes to an external
viewer. The original file remains available through the same scoped download
boundary.
Every run_code call declares artifact_targets: the bounded relative names
of requested output files, or an empty list for compute-only work. This is a
required tool-schema field, not a prompt convention. Pi can therefore publish
non-downloadable generation placeholders before execution and morph each one
into the matching durable native file after guarded persistence. A placeholder
names its call by the same run-bound room reference as the tool frame that
carries it, so the call's settling frame can match it. A call that settles
without its file removes the placeholder. If that frame is lost, the durable
journal settles it instead: either the call's failed completion, or the run
that opened it ending without the file, closes the Artifact pane.
Pi's tool_call callback is only an enforcement seam; it does not author
policy. Kaizen computes the decision in the shared Python guardrail chain. For an
interactive wait, the callback remains pending while the member decides. For a
task, it returns promptly so the conversation run can enter durable
awaiting_approval. Before either request becomes approvable, Kaizen explicitly
releases any sandbox affinity worker. A one-shot approved receipt is consumed
at the execution callback, which reruns the same guardrails before invoking the
tool. A matching pending receipt is rejected at that callback before the tool
executor is entered, and a missing approval store fails closed for any call
whose resolved policy requires approval.
Artifact revision targets follow the same boundary. An explicit
artifact_id is authoritative after owner-scoped resolution; the
server-resolved focused artifact is only a fallback when the tool call omits an
ID. A mismatched focused panel can therefore never redirect an explicit update
to another artifact. After any required approval, the artifact stream opens
the next owner-only version before content generation becomes visible. Deltas
advance only the server-correlated building row; interruption keeps that row as
a checkpoint, and retry resumes it. Tool completion freezes the same row, so
the terminal callback does not manufacture an additional version.
When the agent has the staged writer, an unambiguous request for a new game,
app, document, image, illustration, or similar artifact prefers it instead. This
includes SVG artwork when no separately authorized raster-image tool is present.
The preference places the complete staged protocol in the first provider step's
bounded direct set, beside the agent's other directly advertised tools and tool
discovery, so the agent can gather what the artifact needs before it opens a
draft. When the delivery check reads such a request as asking for a native PDF,
Word, PowerPoint, or Excel file and the agent has run_code as well as the
staged writer, the request prefers run_code instead, because the staged writer
produces only text and HTML artifacts. begin_artifact
opens one owner-only draft;
append_artifact advances its sequence and content digest in bounded pieces;
finalize_artifact verifies the exact cursor before sharing or answer
projection; and discard_artifact terminalizes rejected content. Replaying the
same append converges only when its receipt matches. A different chunk, stale
sequence, prior digest, owner, turn, or execution fence fails closed. Tool
results restore the active draft cursor into recovered model context, while an
expired execution cannot resume writing with its old fence.
Shared-room generative UI is a projection of that protocol, not another persistence system. The live bus accumulates the full arguments for each active append call and publishes the same monotonic snapshot to every viewer. A late viewer can therefore reconstruct the current append while the immutable append receipts and the fenced building row remain the recovery and audit authority.
Those four operations are one model-facing protocol. Direct-tool selection
admits the complete authorized group within the existing exposure bound or
defers the complete group to discovery; it never advertises only begin or
append. If a stale captured step still requests an unadvertised staged
operation after a draft exists, Pi journals that denied result, emits one typed
protocol failure, and stops the attempt. The owner-only checkpoint remains
available for a later fenced recovery instead of driving a repeated model-call
loop.
The general artifact roster injected into an agent turn is conversation-scoped.
Artifacts from older chats are not included by title in a fresh chat; explicit
focus or an exact artifact UUID in the current member request is the cross-chat
selection boundary. The roster is not an owner-library allowlist. The agent
passes an exact requested UUID unchanged, and update_artifact resolves it
again under the acting member before writing; missing and foreign roots fail
closed. This prevents unrelated or same-titled roots from turning an
unambiguous create request into an update or clarification.
When that owner-authorized artifact originated in another chat, a collaborative update binds the new immutable version to the current room's public key. The repository validates that key against the claimed room before adding the reference. The artifact's historical origin key is retained as provenance, but cannot redirect or block the exact current-room link.
When a tool fails
A tool reports failure in one of two ways, and they are not equivalent.
The ordinary way is to return a result marked as an error. The model reads it, and can retry, pick a different tool, or answer without it. The turn continues. This is what a tool should do for any anticipated failure — an unavailable dependency, an empty result, arguments that do not make sense. If tool activity ends without a final answer or a created artifact, the shared runtime reports non-delivery and records a failed terminal. Tool calls alone cannot establish that the member's request was completed. Four tool searches without a successful call to another tool end the turn with an explicit explanation; another successful tool call resets that discovery bound. Repeated identical tool failures retain their separate bound.
The other way is to raise. What happens then depends on what the tool declared:
- A read tool that raises is settled into an error result. Its failure reaches the model as content, the turn survives, and the model only learns the exception message rather than anything the tool chose to say. A read tool cannot have half-completed a change, so there is nothing ambiguous to protect against.
- A write or sensitive tool that raises closes the turn. The runtime cannot tell whether the side effect landed, so it refuses to let the model keep reasoning on top of an unknown. This is the ambiguous-write disposition, and it is deliberate.
Two failures are never settled, at any capability: a cancelled turn — the member stopped it, or the inactivity budget expired — and a breach of the runtime's own turn-lifecycle ordering. Both mean the turn is already over.
This mirrors what already happens when the process itself dies mid-tool. On restart the runtime journal rebuilds the dispatched-but-unfinished call as an error result, so the agent sees a failed tool rather than a dangling one. A tool that fails while the process is healthy reaches the model the same way.
Package tools use the package runner's configured request deadline as their outer invocation deadline. While any bounded tool callback is active, it also holds the turn's inactivity lease. This keeps valid long-running work from being cancelled by the generic tool or turn defaults. The shared 240-second tool ceiling remains authoritative.
Every invocation records one outcome — ok, error, raised, timeout, or
abandoned — labeled by tool and declared capability, so a misbehaving tool is
visible on a dashboard rather than only in a member's complaint. A result that
comes back failed is also logged once with the tool's own reason code, exit
status and failure domain, so a first failure is diagnosable from the log rather
than only from a live repro. The result text itself is never logged: for a
relayed caller error it carries the program's own output, which may quote
whatever the program was reading. A tool that fails by raising does not reach
that log; it is recorded as the raised outcome and surfaces at the route.
Whose failure it was
An error result answers a second question besides "did this work": was the tool unable to do its job, or did the tool do its job perfectly and report back a failure that belongs to whoever called it?
Running code is the case that makes the difference obvious. When agent-written Python exits with a traceback, the sandbox started, installed the requested package, ran the program, and returned the error — flawlessly. The traceback is a fact about the program, not about the sandbox. Kaizen therefore keeps the two apart. A tool's own failure counts towards its health, and a tool that keeps failing across genuinely different requests is cut off for the rest of the turn. A relayed caller failure never counts: four failing programs are four programs, and taking the sandbox away is removing the only thing that can fix them.
The distinction also protects the runtime's failure classification from text it
does not own. That classifier reads the failure message to decide what to advise
— it recognizes timeouts, rate limits, and refusals. Program output can contain
any of those words. A PermissionError: permission denied raised by a member's
own script is not an expired credential, and advising the model to re-authorize
something would be worse than saying nothing. A relayed caller failure is
therefore exempt from every text-matching rule. One rule still applies, because
it is true regardless of whose error it was: sending byte-identical input that
just failed is never the repair.
The runtime decides whether a call failed
A tool server can report a failure as an ordinary successful value: the
transport is fine, the envelope carries no error flag, and the body says
status: error. Reading only the envelope would take that at face value, mark
the call a success, and hand the model an error body it is free to narrate as a
completed write — with nothing left downstream able to contradict it, because
every later guard, including the mutation grounding that can overwrite a
confabulated answer with the truth, keys off the recorded outcome.
Kaizen therefore reads the body as well as the envelope. A structured result or
JSON text block whose own members report a failure — an error member, a
failed status, state or outcome, or success/ok set false — resolves to
an error outcome even when the server flagged nothing. Whether a call failed is
the runtime's decision; a remote server is not trusted to make it, and one that
never learns the flag is still reported honestly.
A failed call can still have saved files: a sandbox script may write its output and then raise. Grounding then names the saved files and says the call exited with an error and the change may be incomplete. It does not claim the change never happened while the member can already open its output.
A dropped canvas retracts its own claim
Rich tool output is moved out of the model-visible payload and rendered on the chat canvas, leaving the model a marker saying the result is on screen and should be summarized rather than reproduced. Materializing that canvas is additive and never turns a successful call into a failed one, so a presentation failure drops the canvas and lets the result stand.
The marker is written before that point, so dropping the canvas has to withdraw it too. Kaizen replaces any surviving on-screen claim in the payload with a statement that nothing was rendered and that rows or images the model was not given must not be described. Otherwise the one remaining instruction about the result would be to summarize a table that no longer exists anywhere — and the answer would confidently describe content nobody ever saw.
Asking twice for the same thing
A model that re-issues a call it has already run is a familiar pathology, and smaller locally served models fall into it readily. For a tool that only reads, the cost is a wasted round. For a tool that writes, the cost is a second object the member can see: a single request to create one artifact has produced nine distinct artifacts, one every three seconds, from a tool that succeeded every time.
The runtime already knows the first call committed, so recognizing the second needs nothing from the tool — and the rule is written once in the shared execution seam rather than inside whichever tool happened to be abused. A write whose tool name and arguments exactly match one that already succeeded in this turn does not reach the tool at all. The earlier result is handed back instead, with the identifiers the model needs to reference what exists and a plain statement that nothing new was created. That is reported as a success, because it is one: what was asked for is there.
The rule is drawn tightly on purpose, and the tightest part is which tools it applies to at all. Being a tool that writes is not the test — most writes are not repeatable-means-redundant. Running code again after the workspace changed underneath it is the ordinary way that tool is used; clicking the same place on a page twice is ordinary navigation, and the member may have confirmed the second click specifically. Silently skipping either would be a worse failure than the duplicate, so each tool declares for itself whether repeating an identical call would leave a second object. Creating an artifact does. Almost nothing else does.
Where the decision is made matters as much as the rule. An artifact that streams into view is written to storage while the assistant is still composing the request — that is what lets a member watch it appear. So the duplicate has to be recognized at that moment, before anything is stored, rather than later when the tool would have run. Recognizing it too late would mean not creating the artifact but still leaving its shell in the library, unfinished and permanent: the same clutter under a different name.
Beyond that: only a byte-identical argument set counts, because two creates that differ in any way are two intents, and a member really can want two similar documents. Only the current turn is remembered, so asking again later gets a genuine second copy. A write that failed committed nothing and may be retried unchanged. And a confirmation card is a question rather than a write, so the approved call that follows it is never mistaken for the duplicate.
Two identical writes the assistant asks for at the same moment both happen. Asked for two copies of one thing, an assistant that means it requests both in a single step, and that is what the runtime allows. The repeat it turns away is the one decided afterwards — the assistant called the tool, read that it worked, and called it again anyway. That difference is what separates a member who asked for two from a model that lost track of having already done one.
It separates them well, not perfectly, and the residual gap is worth stating. Nothing obliges an assistant to batch: it may decide on two genuinely wanted, byte-identical copies one step apart, and that is indistinguishable from the loop this exists to stop. Such a request is then answered with one artifact and guidance to vary the arguments if a second is really wanted, and a follow-up message creates it. The trade is deliberate and asymmetric — the cost of being wrong this way is one artifact the member can ask for again, and the cost of being wrong the other way was nine they had to delete.
A suppressed write is counted under its own outcome in the tool-run metric. A write that quietly does not happen is the same kind of silent wrongness as the duplicate it prevents, so an operator can see it without reading logs.
Being told to stop is not the turn ending
When the runtime does refuse a repeat, it refuses with a result. The model reads the same sentence it would have read anyway, keeps everything the turn has already built, and can answer the member. Only a genuine policy or authorization block fails the turn closed. A model that over-attempted one tool must not cost a member the work of the whole turn.
A refusal costs nothing to issue, so a model that keeps sending the same thing keeps being refused. After a few, the wording stops coaching and starts closing: the assistant is told to stop calling that tool and answer with what it has. An unbounded loop of free refusals would spend the turn's own budget and end it just as dead.
Execution bounds
Each tool declares how long one invocation may take. A tool that outruns its bound is cancelled rather than left running, which is what releases its database session, connector connections, and sandbox lease. A tool that omits a bound gets a conservative default, and a declared bound is capped so no single tool can hold a member's turn indefinitely.
Execution also watches for a caller that has gone away. If the turn is cancelled or the calling sidecar stops waiting, the running tool is cancelled too — the same disconnect awareness the decision seam already had. Both outcomes are reported as tool failures rather than policy denials, because the call did run and did hold resources.
Parallel calls
AGENT_LOOP_MAX_PARALLEL_TOOLS bounds concurrent tool calls, defaulting to 1.
Raising it improves latency on genuinely independent reads and increases the
blast radius of a bad turn, so it is bounded server-side rather than chosen by
the model.
Tool discovery
With AGENT_TOOL_DISCOVERY_ENABLED, an agent can ask what tools are available
to it rather than having the full descriptor set injected into every prompt.
This keeps the prompt smaller and keeps the roster from leaking into contexts
that never needed it.
Discovery returns what the agent may use — it never expands the grant.
Searching is idempotent: a matching tool that is already callable is still
reported instead of being mistaken for a missing capability, while a deferred
match becomes callable on the next model step.
An exact authorized tool name in the current member request receives a bounded
first-step visibility slot. The agent calls that tool directly; it does not
replace execution with a discovery lookup. This preference changes visibility,
never the member, agent, or deployment grant.
The conversation's artifact roster earns the same slot. When the roster reports
an unfinished version and tells the agent to complete it with update_artifact,
that tool is placed on the first step's catalog ahead of metadata matches. A
continuation such as “please finish that” names no tool, so matching alone can
fill the bounded set with whatever the wording resembles and leave the staged
new-artifact protocol visible while the one tool that completes the open version
waits behind discovery; the agent then starts a same-titled duplicate. The
instruction and the advertisement are derived from one definition, so the roster
cannot name a tool the catalog is hiding. As with every preference, this changes
visibility only.
Multi-call protocols remain atomic inside that bound. Preferring one operation
can expose its complete authorized protocol, but it cannot make only a prefix
callable on the captured provider step.
When the request uses the stricter imperative form “must call tool” or
“use/call tool exactly once,” and names only one authorized tool, Kaizen also
binds that tool into the signed run policy. For that provider step Pi
advertises only the already-authorized required tool, and that narrow
advertisement is the whole of what the provider sees. Pi no longer sends a
forced tool choice alongside it: the runtime withdrew that option, and nothing
in the OpenAI-compatible protocol replaces it. The requirement never rested on
it. Pi holds substitute calls, suppresses answer text while the named tool is
pending, and turns a text-only reply into a retry, so a model that answers in
prose instead of calling the one tool it can see costs a round trip rather than
escaping the requirement. Nothing is granted or executed on the provider's side
either way; the ordinary Python callback remains the sole decision and
execution boundary. The narrow advertisement clears immediately after the
required tool is attempted, so evidence synthesis can use the full frozen
catalog.
Discussion, negative instructions, multiple requested tools, and names absent
from the frozen catalog create no requirement. Neither does a request for a new
artifact or an inline canvas: it names no tool, so it only prefers its writer
and never narrows the provider step to it. A server-authored specialist
routing grant takes precedence over member wording.
The connector and MCP catalogs remain directly callable baseline reads even
when an agent's corresponding dynamic allowlist is empty. In that case they
return an authoritative empty inventory. The underlying connector and MCP
operations remain absent, so capability discovery cannot turn into authority.
Request-scoped platform tools
Kaizen has two dynamic platform sources: Kamiwaza Core's live OpenAPI document and administrator-enabled MCP Guard servers. MCP Guard remains specific to MCP; it is not the contract for applications, connectors, skills, workrooms, or other Core services.
The generic broker reads /api/openapi.json as descriptive metadata and
compiles authenticated operations into namespaced kz_api_* tools. Tags form
administrator-facing surfaces. A normal authenticated operation added under a
new or existing tag therefore appears without a Kaizen release; no app name,
route, connector type, or skill identifier is hardcoded. Public/auth-exempt
routes are excluded. Explicit role metadata removes incompatible operations
from discovery, and Kamiwaza Core remains the final authorization authority on
every call.
Both sources are resolved through the validated member's forwarded platform envelope, then intersected with deployment policy, the agent ceiling, and the member's per-conversation choices. Admin policy is a four-level tree: Kamiwaza root, source family, discovered surface, and exact endpoint. Root and source selectors are tighten-only ceilings and never grant descendants by themselves. A surface selector can admit future operations on that same Core tag, SDK surface, MCP server, or external source, while an exact disabled tool remains an exception. An explicitly disabled ancestor is a hard ceiling: an old exact allow cannot re-enable an operation beneath it. Context, Skills, Workrooms, and every other Core tag are independent, so disabling one service leaves the others unchanged. A missing envelope, unselected capability, incompatible role, or discovery failure produces no callable operation.
An administrator may bind one reviewed public-search MCP operation to the
General assistant for a specific workroom with an explicit
"general_enabled": true on the binding. A legacy binding without that flag
does not grant General access. An approved exact binding can admit the
constrained General search adapter when the global platform-tool selection is
empty; an explicit disabled root, source, server, or operation still denies it.
Data also requires global selection and limits its query to the member's
current message. The exact bound operation is removed from ordinary MCP catalogs
for Personal, Data, and default runs even if global selection is later widened
or general_enabled is false or absent. An unbound, globally selected Serper
operation remains available to Personal and default runs; Data still denies its
unreviewed raw query path.
Every raw MCP callback re-reads the binding inventory, so an operation bound
after a run started loses ordinary callback access. If that policy read fails,
the raw MCP call is denied while unrelated built-in and Core tools remain
available.
Generated descriptors are deferred. The model initially receives only the
bounded tool_search descriptor. Its surfaces view returns at most eight
authorized parent summaries with counts and effects but no operation names or
schemas; its tools view makes matching exact tools callable on the next step.
Search indexes compact parent manifests for Core tags, MCP
servers, and other sources separately from exact operation documents. It ranks
at most eight matching parents, expands a bounded number of their members, and
returns exact callable descriptors—never a callable parent placeholder. Parent
terms are not copied into the global operation postings. A catalog with
thousands of operations therefore neither injects thousands of schemas into the
prompt nor turns a broad service query into an unbounded scan. When operations
under one parent publish different bounded surface descriptions, the parent
index accumulates those descriptions so a later surface remains discoverable
without duplicating its endpoints or scanning the full catalog. OpenAPI
metadata is cached by API base with ETag revalidation, a short freshness window,
bounded stale fallback, single-flight refresh, and shared connection pools.
This cache contains schemas only—never response data, credentials, or
per-member authorization results.
Schema refresh parsing and compilation run outside the API event loop. Each resolved input contract must pass an independent JSON Schema 2020-12 meta-schema check before it enters the shared catalog. Reference and container depth, descriptor bytes, and operation count remain bounded. A malformed, recursive, oversized, or otherwise invalid endpoint is omitted by itself; it cannot poison the other endpoints or a later provider request. The broker never repairs invalid metadata into a weaker callable contract.
Search normalizes singular parent names and a small set of setup terms while retaining BM25 operation ranking. A constructive request can return supporting read operations, but it does not expose destructive siblings merely because they share a parent surface. This makes a request to configure a connector rank its create/update flow and the dynamic type-schema read needed to populate provider configuration without adding delete or disconnect operations to that model step.
Before an outbound MCP request, the trusted backend promotes the validated request-scoped platform session token to a bearer when the browser envelope has no explicit authorization header. The token remains server-side, the workroom header remains attached, and an explicit authorization header always wins.
Every granted tool is projected to a descriptor at one point before the catalog is digest-bound and frozen, and that projection strips length bounds at or above 2,000 from the schema it advertises. A locally served model turns the catalog into a grammar, and its parser rejects the whole grammar — every tool in the offered set, not just the offending one — when a single repetition bound reaches that threshold. The rejection is logged by the model server and not returned, so the turn proceeds unconstrained and structured output stops being enforced with no error anywhere. Stripping at the projection covers first-party, MCP, OpenAPI, connector, and studio tools together, including a remote schema Kaizen did not author. The tool's own declared bound is unchanged and still enforced when the tool runs.
The live descriptor is authoritative for the tool name, input schema, and
read/write annotation. For Core operations, x-kamiwaza-effect may declare
read, write, or sensitive. Without that annotation, GET, HEAD, and OPTIONS
are reads, DELETE is sensitive, and every other method is a confirm-gated
write. Agents must not infer a safer effect from an SDK package, route name, or
previous run. Results return through the same bounded projection and audit path
as other tools. The broker is not read-only: authenticated POST, PUT, PATCH, and
DELETE operations are included when the policy intersections admit them.
write and sensitive descriptors enter the shared approval mechanism;
Kamiwaza authorization is still checked on the eventual call.
The request URL, method, path fields, query fields, identity headers, and
workroom are fixed from the compiled operation and current envelope. Model
arguments cannot provide an endpoint, header, user, role, token, or workroom.
JSON and text results are bounded, and credential-shaped response fields and
signed-URL query values are redacted before model projection. Core server-error
details are not projected. Other binaries return digest metadata and are not
persisted. A requested skill package is the narrow exception: the
broker may read a bounded safe SKILL.md from its ZIP into that tool result for
the current run; it neither installs the package nor executes or retains its
scripts.
Credential intake is not a model-facing API operation. The Core compiler
recursively omits operations whose ordinary path, query, or body can carry a
plaintext password, token, private key, credential-shaped field, writeOnly
secret, or an unstructured authentication, provider, connector, or connection
credential container. Compound fields such as connection_config remain
quarantined. A generic non-secret config object remains callable so future
connector types can publish settings such as Website sites without a Kaizen
release. An agent must resolve that resource type's live configuration schema,
place settings at the write operation's declared path, and perform an
authorized read after the write. The requested and persisted non-secret
settings must agree before the turn reports success; an empty or mismatched
configuration is a failed write. Immediately before execution the broker recursively rejects
credential-shaped keys and credential-bearing URL components, then forwards
only the safe body under the current member envelope and normal write approval.
An operation may accept an opaque credential_ref; the referenced value
remains owned and resolved by the trusted platform. Credential- and
authorization-shaped response fields are redacted as a second boundary.
External OpenAPI authentication uses the separate browser ceremony below; its
values never become tool arguments or model-visible results.
Provider errors that identify an invalid bound request, tool schema, model id, or provider-specific parameter are deterministic for that step. The Turn Engine records one content-free diagnostic, commits its normal durable terminal checkpoint, and does not replay the identical provider payload. Transient transport failures retain the bounded pre-commit retry policy, and ambiguous writes remain terminal.
Kamiwaza Context Service participates through the same contract. Its
authenticated /context/* operations are searchable tools and run only when
the member asks for context work and the model selects the matching tool. A
Context collection is a workroom retrieval index, not a Library Collection.
An unqualified "collection" means the Library Collection: filing, uploading or
ingesting into a collection gives manage_library first-step priority, and
listing the member's collections ("my collections", "show collections", "which
collections do I have") gives inspect_library priority. Other phrasings get
no intent priority and fall back to the metadata match. The Context tools get
intent priority only when the request names the vector meaning explicitly
(Kamiwaza Context, vector, embeddings, RAG or a retrieval index; a context,
vector or embedding collection; or an ontology). The ordinary word "context"
("upload this file for context") is not enough, and a plain "upload this
document" no longer boosts the Context write tool. The Library filing rules skip
a collection noun directly qualified as a vector container ("my vector
collection"); other placements of vector wording are not yet recognized. The default persona states the same rule
and asks only when the meaning is genuinely unclear. The Context write tool
can still appear among the first-step metadata matches, depending on the granted
catalog, so the two tool descriptions spell out the difference and tell the model
to say it cannot create a Library Collection rather than substitute a Context
one. A Context write that gets no answer (a timeout, a lost connection, a
server error, or a 503 that is not Core's own error body, such as a gateway
"upstream connect error", or an empty or HTML body) is reported to the
member as having an unknown outcome, and so is a write whose reply could not be
read. Only a 503 carrying Core's error shape (an error code, or Core's own
detail rather than a bare "Service Unavailable" or wrapped proxy text) counts
as Core refusing before it did the work, with one exception: when the SDK already re-sent the write (a
Core 503 whose code is on the SDK's replay allowlist and carries the
retry_after_seconds mirror it honors, a positive number of at most 30
seconds), several attempts went out, so the
outcome is reported as unknown. A retry hint on any other Core 503 does not
change its answer. A write Core turned away at sign-in (a 401, which the SDK
raises without a status code) tells the member to sign in again and ask for
the change again, rather than reporting an unreadable reply. Context writes fail closed: every failure gets a fixed member-safe
sentence, and only a Core request-validation refusal (400 or 422) contributes
its own detail, bounded to 120 characters; any other 4xx body is not quoted, and
a 408 is reported as an unknown outcome. A failure inside Kaizen's own Context writer is reported as
an internal error, still with an unknown outcome. From a Context write, no exception text, URL, host or port
reaches the model, the member or the chat.tool.write audit row; the one
sanctioned exception is that bounded 400/422 validation detail, which is
Core's own text and is quoted as Core wrote it. Each failed write logs one WARNING
for operators with the operation, status, Core error code, classified outcome
and the exception chain, plus a reference that also
ends the member's answer and the audit row, so the three can be joined. An
operation Core's Context backend does not support (501) gets a fixed
member-safe answer instead of Core's raw response. A write tool without its own
error projection never relays exception text, which can name an internal host
in any form: the model, the member and the audit row get a fixed sentence with
the exception type and a reference. A failed mutation is reported as an unknown
outcome, because a tool can commit before it raises; a failed access check is
reported as an internal error in that check, with no outcome claim (an
authorize callable must not mutate). Artifact writes the store refuses before
committing (a version conflict or the renderable-artifact authority check) are
projected as definite "nothing was saved" answers; a streamed generation whose
checkpoint was fenced (taken over, stopped, interrupted or failed) is reported
without that claim, because its streamed
draft was saved when the checkpoint opened. Kaizen's own member-facing errors
(MemberError, LibraryFilingError) keep their text. One operator WARNING
carries the same reference and the real exception chain, with credential-shaped
values redacted and bounded to 2 KB. The
broker does not prefetch, ingest, mirror, or index Context Service records.
Kaizen's existing kamiwaza_retrieve remains the optimized retrieval path. It
calls Core's published unified search with formatted context, returns the
context, source references, per-collection failures, and result count to the
model. Unified search does not bound the formatted context, so the tool
truncates it to 4,000 characters, the bound Core's retired retrieval route
applied. A search in which no collection
could be searched is reported as a failed tool call rather than as zero
results; a failure beside a collection that searched cleanly is still an
answer. Named collections that Core would reject fail the call instead of
widening it to the whole workroom. Generated POST reads remain conservatively write-classified until Core
publishes an explicit effect annotation.
Generated operations are currently private-turn capabilities. Shared chats use a closed audience-safety classification, and a new dynamic operation has no reviewed audience class by definition, so it is omitted there. Workroom forwarding scopes a Core request; it does not by itself make an actor-private result safe to retain for every member of a collaborative room.
External OpenAPI sources
Members can register an uncredentialed HTTPS OpenAPI URL and its HTTPS API base as a private or workroom-visible capability source. They can use the member API or grant the approval-gated Manage external API sources tool and ask Kaizen to create, update, enable, disable, or delete the pointer. PostgreSQL stores only the source id, owner, workroom, visibility, URLs, cross-origin consent, and enabled state. It never stores a copy of the remote schema, endpoint response, or credential.
The management tool uses the same resolved member permission mode as every other write. A one-shot approval and an explicitly selected Never ask policy produce distinct execution-local facts; the adapter accepts either authorized path while preserving the distinction for receipts and audit.
This adapter is protected by the independent, default-off
KAMIWAZA_EXTERNAL_CAPABILITIES_ENABLED switch. Schema fetch and endpoint
execution also require the signed sandbox-egress proxy policy. HTTPS origins
cannot contain user information, query credentials, or fragments; redirects
are denied. The proxy resolves and pins the destination, enforces the
administrator's address mode, host/CIDR and port policy, and rejects protected
addresses. Kamiwaza identity headers are never sent to an external origin.
Enabling this feature is therefore not authority to reach an arbitrary host:
the separate egress policy remains the network ceiling.
The schema URL and API base must share an origin by default. A different API
origin needs both explicit consent on that source and the separate, default-off
KAMIWAZA_EXTERNAL_CROSS_ORIGIN_SOURCES_ENABLED admin ceiling. An API-key
scheme that injects a credential into a query string is omitted from tool
discovery unless the separate, default-off
KAMIWAZA_EXTERNAL_QUERY_AUTH_ENABLED ceiling is on. Header, cookie, Basic,
Bearer, OAuth, and mutual-TLS authentication keep their existing behavior.
OpenAPI securitySchemes compile into a value-free browser form. Supported
contracts include header, query, and cookie API keys; HTTP Basic and Bearer;
OAuth/OpenID access tokens; and mutual-TLS/NPE certificate, private-key, and
optional passphrase fields. Unknown or incomplete alternatives are omitted.
The model receives the endpoint's ordinary argument schema, but never the form
values or credential-injection plan. The signed form plan binds the actor,
workroom, source, operation, schema digest, arguments, effect class, consumed
approval, nonce, API base, and five-minute expiry.
The form prominently displays the signed API destination origin before the
member enters a value.
Submitting the form rechecks the live admin hierarchy and the source's current
workroom visibility, enabled state, API base, cross-origin policy, and
query-auth policy before the nonce is claimed. Disabling an endpoint, its
source, either risk ceiling, or the external parent therefore revokes an
already rendered form; changing the source requires a newly discovered form.
Use once is the default. The browser submits values directly to the trusted
broker, clears its form state, and the broker injects them into exactly one
proxy-routed request. Values are not written to PostgreSQL, Redis, DataHub,
audit events, transcripts, or results. Mutual-TLS material is staged only in
/dev/shm with owner-only permissions while the TLS context loads and is then
removed. A failed attempt requires re-entry. Remember is a distinct literal
user action, available only when KAMIWAZA_CREDENTIAL_REMEMBER_ENABLED is on;
it writes an encrypted bundle to Kamiwaza DataHub/Catalog and returns only an
opaque secret URN. Kaizen exposes no value-read method.
Before any endpoint result reaches the model, the shared response projector redacts submitted credential values under ordinary or credential-shaped keys, in text and error bodies, in URLs and binary metadata, and in bounded skill instructions. It also covers common URL, JSON, Base64, Basic-auth, prefix, and certificate-line representations. This is an echo guard, not a claim that an arbitrary malicious service cannot invent a new covert encoding; the selected destination still receives the credential and must be trusted for that value.
Every signed nonce is claimed once in a durable credential-free receipt before
network I/O. Receipts contain operation identity and terminal status, not
arguments, request bodies, credentials, or results. A transport failure after a
read returns a bounded error. A write, sensitive operation, or DataHub remember
whose delivery is uncertain becomes ambiguous and is never retried
automatically.
External descriptors use the same parent/source/endpoint hierarchy and deferred tool search as Core. Exact tool names encode their source id, allowing the runtime to query only the selected indexed source row. Schema documents are limited to 16 MiB and 2,000 admitted operations, fetched with bounded concurrency, cached with ETag/single-flight freshness and stale fallback, and treated as untrusted metadata. A member may own at most 100 sources. These bounds keep workrooms with many members from turning exact endpoint selection into an all-source scan or prompt expansion.
For a version 2 renderable-artifact declaration, the administrator-approved registration of that MCP server and tool is also the producer trust root. Kaizen validates and canonicalizes the declared envelope at the shared result boundary, then attests that the approved producer returned that validated output. It does not attest that the producer's provenance or security markings are factually true. A careless or compromised producer can mint false fields that still satisfy the envelope contract, so operators must review producers before enabling them and disable or revoke one whose integrity is in doubt.
This is distinct from the mcp descriptors stored in a Pi package manifest.
Package-declared descriptors remain record-only and are not hosted by Kaizen.
Demo Builder does not depend on that dynamic path. Its package remains offline
and secretless while Kaizen's trusted backend exact-pins kamiwaza-sdk==1.2.0
and exposes two closed first-party tools. inspect_kamiwaza returns bounded
model, deployment, dataset, extension, connector, and workroom state. Its
get_workroom_overview operation may target any workroom in the caller's live
Core-visible workroom set without changing the browser's selected workroom.
Only a Kamiwaza Platform Admin sees platform_list_workrooms, a metadata-only
all-workroom inventory. manage_kamiwaza_demo remains preview-bound and
approval-gated; it exposes no raw SDK selector, delete, arbitrary request body,
CLI argument, endpoint, or credential input.
These closed adapters appear under Kamiwaza → SDK → Kamiwaza SDK in Admin →
Tools. They retain their compatibility default unless an administrator
explicitly disables the Kamiwaza root, SDK source, SDK surface, or one adapter.
Each ancestor is a hard ceiling over the adapters below it. The SDK family is a
platform capability, not an authored agent grant: every selected chat agent can
use the enabled adapters. A read-only agent receives only inspect_kamiwaza;
write-capable agents can also receive manage_kamiwaza_demo, whose mutating
operations remain approval-gated. The member's conversation selection can
narrow that set, and a delegated child inherits the parent's selection. Tool
schemas omit Platform Admin operations when the actor lacks that authority.
None of these choices widens the member's authorization in Kamiwaza.
Cross-workroom reads use a disposable header-scoped client after live membership validation. Cross-workroom writes do not simulate enter/leave: Core's current write contract binds the user's server-side session, so Kaizen fails closed until an exact target-workroom effect can be delegated to its workload identity.
Each SDK client is created for one call from the validated member's forwarded
headers, workroom, API base, and TLS policy. The synchronous SDK runs off the
event loop with transport and tool timeouts. Only allowlisted fields return to
the model: no forwarded headers, live endpoint, raw log, host detail, or
unrestricted provider response crosses the boundary. Apply carries the exact
Blueprint, environment, and preview digests from the preceding preview as
deterministic drift checks; those values are not approval credentials. The
adapter lists before create, reuses exact compatible state, and stops on
ambiguity.
An apply tool call pauses at the generic conversation approval item. Its digest
binds the exact validated request, preview, and environment. A resumed call
receives a one-shot server-owned receipt, then rechecks the session actor,
staged digest, live environment binding, and preview gates before it invokes
the SDK once. Denial has no platform effect.
In-flight Demo Builder confirmations use Kaizen's shared Valkey service. Keys are
derived from the validated actor and exact turn, values expire with the action
window, and exact-digest tombstones make replays idempotent across API replicas.
If the record expires or the shared store is unavailable during commit
preflight, Kaizen performs no SDK operation and asks the member to preview again.
A store failure after the SDK response, while recording the replay tombstone,
is reported as an ambiguous failure rather than a successful commit; inspect
exact platform state before previewing or retrying.
The Kaizen-owned Demo Reconciler calls typed SDK services in-process and reuses
the SDK seeding package's workroom-scoping and catalog-installation primitives.
The separate kamiwaza-cli operator application is neither installed nor
launched, so its context files, cached OIDC tokens, process environment, and
human-oriented output are not part of the agent boundary. Workroom changes are
limited to an exact compatible room whose reviewed request explicitly permits
the update. Kaizen and model endpoints are derived internally and omitted from
tool output.
Federated cluster tools
A Kamiwaza federation in shared_idp mode lets this deployment's Core call a
paired cluster through its mesh proxy, /api/mesh/<federation>/…. The paired
(receiving) cluster accepts such a call only with a token issued by the
federation's shared identity realm, never with the member's own sign-in
token. Kaizen therefore carries a second, outbound credential per member for
each shared realm, and three first-party tools use it:
link_federation_identitystarts an OAuth 2.0 device authorization grant against the shared realm's issuer. The member opens the returned sign-in link and approves in their own browser, so no password passes through Kaizen or the model. The issuer's offline refresh token is stored field-encrypted, keyed by the member and the realm's issuer. A background task on the API process is the only thing that polls the issuer, because a device code is redeemed once. It stores the link as soon as the member approves, so the model does not have to call the tool a second time, and a later call reportslinked. If the member denies the grant or it expires, a later call says the sign-in was not completed, and the call after that starts a new grant. A pending grant is held in that API process, so a link must be finished on the replica that started it.federated_readsends a GET to the federated cluster: model and dataset listings, job status and retrieval reads. It is a read tool.federated_writesends a POST, such as a job submission or a retrieval job. It is a write tool that requires confirmation, and its approval card names the federated cluster, the request and the job command.
Both request tools mint a short-lived access token from the stored offline
token and call Core with only that bearer; the member's forwarded envelope
does not travel with the call. The receiving cluster then applies its own
onboarding, grants and gates to the linked identity. Each tool accepts only the
routes a brokered caller may use on a Kamiwaza 1.3 receiver (model listing, the
dataset catalog listing, /api/cluster/jobs/… and /api/retrieval/…) and
refuses anything else before a request is sent. An oversized listing is reduced
to each item's identifying fields instead of being cut off. When the realm
rotates refresh tokens, the replacement from each refresh is stored. The model,
the transcript and the sandbox never receive a token.
Every link and every call is audited: oauth.consent.granted when a link is
stored, oauth.refresh.revoked when the realm refuses it, and one
chat.tool.federated_request per request-tool call. That includes refused
calls, and a call that got no answer (a transport error, or the turn stopped
while it was in flight), because it may still have run on the receiver (see the
audit reference). A write that got no answer
comes back marked not to be retried, and the model is told to check with
federated_read whether it ran before sending it again.
The tools exist only when the FEDERATION_TOOLS_ENABLED flag is on (it ships
off) and an administrator lists at least one federation in
FEDERATION_SHARED_IDP (see Configuration). The
receiving cluster filters what it returns for the linked identity, so all three
are actor-private: the link tool and federated_write are actor-private writes
and federated_read an actor-private read. They run only where the initiating
member is the room's whole audience and are withheld in a chat shared with
others.
Write tools and side effects
A write tool is one whose effect outlives the turn. Kaizen treats three categories differently:
| Category | Examples | Guard |
|---|---|---|
| Member-owned store | artifacts, widgets, tasks | Write guard; owner-scoped; no external effect |
| Isolated execution | run_code | Sandbox lease bound to actor + conversation |
| External systems | Kamiwaza demo ensures and approved package tools | Write guard and approval gate |
An ambiguous external outcome is terminal. Kaizen does not retry a write whose effect it cannot prove did not happen.
The same rule applies to delegated model calls even though model invocation is classified as a governed read. Core reserves both the member charge and the separate workload concurrency/spend ceiling before provider I/O, preserves one correlation identity across every serving route, and settles only from trusted usage evidence. Credential-backed tools receive an opaque broker handle; only the registered broker adapter may turn it into a bounded operation, and no credential value returns to Kaizen or the model loop.
Artifact generation is the narrow exception to terminal journal ambiguity, not
to write authorization. Exact create_artifact, update_artifact, and staged
artifact transaction requests carry a recovery policy only after their owner-,
turn-, and call-fenced durable checkpoint is admitted. Updates and staged
writes open that checkpoint before content streams; creates first require a
complete, unique-key ordinary argument object. If a process stops after
admission, journal reconstruction marks that attempt interrupted and the next
turn resumes the same checkpoint. External and uncheckpointed writes remain
ambiguous and are never replayed automatically.
A create_artifact checkpoint is admitted only after its complete JSON object
arrives. Optional and out-of-order fields are not the only ambiguity: JSON
permits a later duplicate key whose last value becomes authoritative. The
adapter therefore cannot prove from any incomplete create that later kind and
language bytes will not form the reserved renderable-artifact pair. It buffers
all create deltas, parses only the completed top-level object, rejects duplicate
keys and the reserved pair, and materializes a complete ordinary call once. An
interruption before that point terminally fences the call and leaves no durable
partial checkpoint to resume. Restoring live create previews requires an
upstream canonical argument stream that structurally forbids duplicate keys, or
a separate ephemeral view that carries no durable artifact identity.
An admitted ordinary row receives the complete authorized kind, content, title,
and language together, so an html call cannot remain durably mislabeled
because of JSON field order. Updates to an existing owner-resolved artifact
continue to advance their private checkpoint as deltas arrive.
A mutable building, interrupted, or failed checkpoint is not admitted to
a shared-room resource manifest. Only a completed artifact can be pinned with
its immutable digest and size. This keeps an early streaming placeholder from
becoming a stale room authority after the same row receives its final content.
create_artifact owns text and live-canvas formats. A title ending in .docx,
.pptx, or .xlsx is rejected there and redirected to run_code, which can
produce the native binary package in the isolated sandbox. The run result is
structurally validated before its files pass through the guarded, owner-scoped
artifact persistence path. That path keeps one copy of an unchanged file: a file
whose bytes and content type match the latest version stored under the same name
for the same member and session reuses that version, and the run reports it
under that version's id. The check and the insert are serialized per member,
session, and file name.
For run_code, attached or explicitly requested documents are projected under
a server-assigned read-only directory. A signed manifest carries the document
identity, exact content digest, size, and path. The sandbox's resource
projection validates the manifest digest before dispatch. It does not count a
new file the same run linked after it started as a change, but it refuses a new
version of a file the turn was admitted with. The run audit records the bounded
resource list. The model cannot add a file by placing a path or identifier in
tool arguments.
Typed graph writes
Graph artifacts cross the same owner-scoped write boundary, with an additional structural check before persistence. A rejected graph is not partially created or revised. The tool response returns the complete canonical node, edge, source, and reference contract so the agent can repair all field mismatches in one pass; the validator does not accept model-invented aliases into durable storage.
Reviewed shared-pipeline extensions
Kaizen can bundle source-reviewed Pi extensions that run once in the shared pipeline host. They are product code, not member-installed package extensions. A static descriptor fixes each extension's ID, version, host compatibility, execution order, allowed hooks, and optional reviewed tool-package identity. Manifest references only enable an exact descriptor. They cannot choose a module path or change execution order.
The shared host supports bounded context, before_provider_request,
agent_start, agent_end, agent_settled, turn_start, turn_end,
tool_call, and tool_result hooks. It rejects provider headers and
registration, model selection, session mutation, UI, shell execution, direct
message or journal publication, commands, flags, shortcuts, and renderers.
Hooks receive credential-free copies. Context, provider, tool-call, and
tool-result changes remain private until all handlers finish and the host
validates the complete result.
An extension tool declaration does not provide execution authority. At run
admission, the sidecar requires its name and parameter schema to match a tool
in Python's frozen authorized snapshot. The sidecar discards the extension's
executor and binds the existing callback tool. Each call still crosses the
authenticated Python decision and execution callbacks. Python then runs
first-party code or sends an approved package tool to the secretless
package-tool-runner. A missing or mismatched declaration stops the run before
provider or tool work.
Packaged tools
Some capabilities come from packages, which may include third-party code.
That code runs in the package-tool-runner — a separate, secretless
environment, deliberately without the tokens or keys the trusted backend holds.
This is the admitted tool-execution boundary. Package proposal tests and native Pi loader validation use the isolated sandbox worker before admission; they do not execute in the package runner or grant the proposal runtime authority.
An untrusted package executing successfully still cannot reach anything sensitive, because there is nothing sensitive in its process to reach. See Isolation boundaries.
A package's npm dependencies must resolve before its extension loads. For the
agent packages Kaizen ships (pi-hypa, pi-lens, pi-web-access), the runner image
pre-installs them at build time from exact lockfiles under
sidecar/pi/bundled-package-deps/, once per architecture so native binaries
such as Hypa and ast-grep match the image. The runner links an admitted
artifact to its tree by the artifact's own integrity hash, so only those exact
bytes get it, and never calls npm for a bundled package; a bundled package
whose tree is missing is an image defect and fails rather than reaching for the
registry. Only a package the image does not bundle installs its dependencies
from npm at first use, with install scripts disabled, and PKG_RUNNER_OFFLINE=1
refuses even that with an immediate, explicit error. Re-pinning a vendored
package means regenerating its tree with
scripts/update_bundled_package_deps.py; a boundary test fails until the
manifest matches every vendored artifact that declares dependencies.
Compression at the tool boundary
Where a member has opted in, eligible tool-result projections cross one
authorized seam where Tool-result compression may shorten the model-bound
copy of public sandbox run_code stdout and stderr.
The raw authorized result stays canonical for audit, presentation, and diagnostics. Search, retrieval, and any source-bearing evidence bypass this seam byte-for-byte. Compression failure returns the original result and continues the turn — it can never fail or widen a tool call.