Skip to main content

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.

ToolKindWhat it does
searchreadRetrieval over the authorized corpus
conversation_history_searchreadAuthorized prior-work discovery
kamiwaza_connector_catalogreadRequest-scoped connector inventory and readiness
kamiwaza_connector_ragreadFederated live folder, file, and evidence discovery
kamiwaza_mcp_catalogreadRequest-scoped inventory of MCP tools authorized for this run
list_available_agentsreadThe member-safe agent roster
search_aboutreadSearch the exact About Markdown bundled with the product
query_documentreadRead one of the member's documents
request_documentreadRe-authorize a retrieved document and add its exact file to this turn's sandbox projection
visualize_read_mereadPresentation guidelines loader
inspect_tasksreadOwner-scoped Task, Goal, progress, and run inspection
manage_taskswriteOwner-scoped Task mutation, one-shot background work, and bounded Agent loops
run_codewriteExecute code in the sandbox
open_browser_sessionwriteLease a shared browser worker and open one HTTPS page
read_browser_pagereadRead the shared page as a bounded accessibility tree
act_on_browser_pagesensitiveClick, type, scroll, or press a key after member confirmation
read_workspacewriteRead full or ranged numbered UTF-8 text under a trusted four-hex Hashline snapshot
edit_workspacewriteApply a guarded OMP-compatible multi-file Hashline transaction
create_artifact / update_artifactwriteSingle-call full-content writes to the member's versioned artifact store
read_artifactreadRead up to 400 numbered lines from the latest owner- or active-room-authorized artifact
edit_artifactwriteAtomically apply version-bound Hashline ranges while streaming a provisional pane preview
begin_artifact / append_artifact / finalize_artifact / discard_artifactwriteFenced staged creation for substantial artifacts
show_widgetwriteGenerative-UI artifact
inspect_libraryreadIdentity-only listing of one Library surface: documents, artifacts, collections, prompts, projects
manage_librarywriteFile an existing Library item into a Collection or link it into a Project
invoke_agentreadDelegate to a sub-agent without granting new authority
Studio readback toolsreadOwner-scoped proposal indexes, exact draft views, bounded package-file reads
Studio mutation toolswriteClosed 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 the member.collection.doc_added audit 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:

  1. 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.
  2. 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:

  1. 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.
  2. The member's current permissions are re-resolved.
  3. The capability ceiling is checked.
  4. 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.
  5. For anything with an external effect, the write guard and the tool's own resource authorization are applied.
  6. 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.
  7. 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.
  8. Citation and presentation lineage is captured.
  9. 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_identity starts 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 reports linked. 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_read sends a GET to the federated cluster: model and dataset listings, job status and retrieval reads. It is a read tool.
  • federated_write sends 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:

CategoryExamplesGuard
Member-owned storeartifacts, widgets, tasksWrite guard; owner-scoped; no external effect
Isolated executionrun_codeSandbox lease bound to actor + conversation
External systemsKamiwaza demo ensures and approved package toolsWrite 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.