Building a tool or package
Three different things: adding a built-in tool to Kaizen's closed registry, publishing an authenticated operation through Kamiwaza Core's generic broker, and shipping a package that brings capabilities with it.
Adding a tool
The registry is closed
AGENT_LOOP_TOOL_NAMES names every tool the loop may register. It is
asserted by a boundary test that walks the source, so the cardinality check
fails the build if the set changes without the trip-wire being updated.
This is deliberate friction. Every new tool expands the loop's trust surface, and that should be a decision, not a merge.
The two trip-wires
Adding a tool means touching two boundaries:
- The closed set in
chat_pipeline/tools/__init__.py, with a comment explaining why the tool exists and what kind it is. - The boundary test that asserts registry cardinality.
If you find yourself editing one without the other, stop — that is the check working.
Conditional registration
Registration is per turn and conditional. Follow the existing patterns:
run_codeis built only whenSANDBOX_ENABLEDand offered only to an agent whose grant explicitly names it;- studio tools register only when Agent Studio is enabled and only for the source-defined authoring agents;
- retired ticket tools and bespoke action-card protocols do not register;
Baseline product reads may register for every chat agent. search_about is the
reference pattern: it reads a build-time copy of the canonical SPA Markdown,
returns bounded excerpts, and declares tool-owned natural-language intent hints
so product questions receive first-step visibility without a central intent
switch. Those hints can only prioritize a tool already authorized for the run;
they never grant one.
The other baseline presentation-only tool is show_chat_canvas. It accepts one
bounded Markdown item only when the member explicitly asks for inline canvas
output, returns it through typed chat_canvas_outputs, and does not persist or
execute the content. Keep this distinct from artifact creation: adding a
temporary display must not acquire a saved-artifact identity or bypass the
artifact write boundary.
A tool being in the closed set does not mean it is offered. Default to not granted.
A Prompt step is one reviewed way to add a Tool beyond an Agent's defaults. The editor stores an exact structural attachment, and the backend resolves it under the running member and effective step Agent on every validation and run. Do not teach a Tool to recognize its own name in prompt prose; content is not a grant. The execution-only overlay still passes through the ordinary tool boundary and cannot exceed the Agent's capability ceiling or administrator policy.
run_code requires artifact_targets on every invocation. File-producing
calls list the bounded relative output names; compute-only calls send an empty
list. Keep this field required when evolving the descriptor so every runtime
can emit deterministic generation placeholders without inferring filenames
from model-authored code.
Publishing through the Kamiwaza broker
A Kamiwaza application or service does not need a Kaizen adapter when Core
already publishes its operation in /api/openapi.json. Give the operation a
stable unique operationId, a useful tag, bounded path/query/body schemas, and
normal platform authentication. Core's OpenAPI auth metadata makes the route
eligible and lets Kaizen omit an explicitly incompatible role. Public or
auth-exempt routes are deliberately excluded.
Declare x-kamiwaza-effect as read, write, or sensitive when HTTP method
alone does not describe the effect—for example, a POST search should say
read. Without it, GET/HEAD/OPTIONS are reads, DELETE is sensitive, and other
methods are confirm-gated writes. Malformed or incomplete operations are
omitted or conservatively classified; Kaizen does not guess a safer contract
from prose, an SDK client, or an application name.
The broker supplies the current API base, identity, role envelope, and workroom.
Do not add those as operation arguments. It returns bounded JSON/text, metadata
for unretained binaries, or a bounded SKILL.md from a requested skill ZIP.
It never installs application code, runs package scripts, or turns discovery
into data ingestion. New tag groups and operations then flow automatically
through administrator selection, agent grants, member narrowing, lazy tool
search, approvals, audit, and Core authorization.
Keep each generated parameter schema below 64 KiB and operation names unique after provider-safe normalization. The broker omits oversized schemas and every member of an ambiguous-name collision rather than guessing which operation to invoke. Response projection redacts credential-shaped keys and signed-URL secrets, but a service should still return the least sensitive response needed for the operation.
Do not publish plaintext credentials as agent parameters. Mark secret input
writeOnly and split the operation into a trusted credential-intake ceremony
plus an operation that accepts an opaque credential_ref. The compiler also
rejects password/secret/private-key formats, credential-shaped property names,
and unstructured authentication or configuration objects. This includes NPE
and mutual-TLS material: certificate bundles, private keys, and their passwords
belong in a binary-safe trusted intake surface, not JSON generated by a model.
Kamiwaza Core operations accept only the forwarded platform API base. A member can separately register an uncredentialed HTTPS OpenAPI source through the reviewed external-source adapter; the trusted backend stores its source pointer and routes calls through signed egress grants. Never reuse the caller's Kamiwaza identity headers for that external origin or accept a model-selected schema or operation origin.
request_document is an example of a composed conditional read: it appears
only in a private turn where authorized search and run_code are both present.
It accepts a retrieved uploaded-document chunk, rechecks the visible parent,
and changes only execution-local context so the exact digest-pinned file can be
mounted read-only. It is not a general identifier resolver and persists no
grant.
Length bounds in a parameter schema
Declare the real limit. A maxLength or maxItems on a tool parameter is a
storage and ingest bound, enforced where the payload is actually stored, and it
should say what that code enforces — a canvas body bounded at 100,000
characters declares 100,000.
Kaizen strips any bound at or above 2,000 from the descriptor it sends the runtime, so a large declared limit never reaches the model's schema. This is not cosmetic. A locally served model compiles the tool catalog into a grammar, and its parser refuses a repetition bound that reaches that threshold — then answers the turn with no grammar at all rather than reporting the refusal. One oversized field would otherwise stop structured output being enforced for every tool in the turn at once, and the only trace would be a line in the model server's log.
Two consequences for a tool author. A bound below 2,000 is passed through and can constrain the model on a locally served deployment, so it is worth declaring accurately on short fields; a hosted model treats it as advisory. A bound of 2,000 or more is advisory everywhere, and neither is a substitute for validating the argument when the tool runs.
Read versus write
Declare the kind honestly.
| Kind | Meaning | Consequence |
|---|---|---|
read | No effect outlives the turn | Passes the read-dispatch path |
write | Effect outlives the turn | Crosses the write guard; may need approval |
A tool that persists anything is a write tool even if it only touches the member's own store. Mislabelling one is the fastest way to create a hole.
What a tool implementation must do
- Execute as the initiating member. Never with an ambient or elevated identity.
- Use the registered resource contract. For a delegated run, let Core classify the action, canonicalize its digest, reserve quota/approval and credential binding, and issue one sender-constrained effect capability.
- Consume before protected code. The registered resource guard must consume the exact capability under the current run claim and fence before any model, credential, read, or mutation crosses that boundary.
- Return a bounded, member-safe projection. Not raw system output — the projection is what stops credentials, ACL lists, or another member's content reaching model context.
- Capture citation and presentation lineage where it produces evidence.
- Fail closed. An unprovable authority is a denial, not a degraded call.
- Never widen on failure. A failure returns less, never more.
- Report failure by returning, not by raising. Return a result marked
outcome="error"for anything you anticipated — a dependency that is down, an empty result, arguments that do not make sense. The model reads it and can recover. Raise only for a genuinely unexpected fault; see below for why the difference matters. - Declare
timeout_sif your work is slow. One invocation is bounded, and a tool that overruns is cancelled so its session, connections, and any sandbox lease are released. Omit it and you get a conservative default. Registry package tools are different: Kaizen owns their runner call, automatically appliesPKG_RUNNER_TIMEOUT_SECONDSto both the request and the outer tool invocation, and holds the turn's inactivity lease while that bounded call is active. Do not duplicate that deployment limit in package metadata.
Returning an error is not the same as raising
Both report failure, but they reach the agent differently:
| Read tool | Write / sensitive tool | |
|---|---|---|
Returns outcome="error" | Model sees your message, turn continues | Model sees your message, turn continues |
| Raises | Settled into an error result; model sees only the exception text | Turn closes — the runtime cannot tell whether your change landed |
A raise from a write tool therefore costs the member their whole turn. Even from a read tool, raising throws away whatever explanation you would have written. Return the error.
Cancellation and turn-lifecycle breaches are never settled this way — those mean the turn is already over.
What a tool must not do
- Hold a model credential — model calls go through the gateway.
- Accept identity from its arguments.
- Implement its own audit path.
- Decode or persist workload assertions, DPoP keys/proofs, Core capabilities, one-use consumption tokens, broker handles, or provider credentials.
- Trust a resource ID, action classification, request digest, approval, quota, credential binding, or capability supplied by model/tool arguments.
- Implement streaming, retries, salvage, or compaction — those are the shared pipeline's job.
Artifact range contract
read_artifact and edit_artifact are core-owned adapters, not package tools.
A ranged read returns at most 400 numbered lines plus the exact artifact
version and content tag. An edit accepts at most 32 non-overlapping ranged
PUT or CUT hunks against that immutable base. Packages may describe when
their agent should use these tools, but must not replace owner resolution,
room-version authorization, WriteToolGuard, optimistic concurrency, or
artifact-kind validation.
Graph artifact contract
create_artifact and update_artifact accept kind=graph as a strict JSON
wire format. The root requires schema_version: 1, nodes, and edges; it may
also carry title, status, scope, and sources.
- A node requires
id,label, andtype. - An edge requires
id,source,target, andlabel;typeis optional. - A source requires
idand may carrytitle,source,excerpt, andtarget_path. - Every edge endpoint must name an existing node. Every value in a node or
edge's
source_idsmust name an entry insources.
These names are literal. Do not emit node_type, relation_type, source_id,
url, or evidence; map them to node type, edge type plus a readable
label, source id, target_path, and excerpt. A structural rejection
returns this complete contract in retry.expected_schema, and no artifact
version is written.
Renderable artifact envelope
Use the versioned renderable-artifact envelope when a tool or reviewed skill
needs to retain structured remote data as a durable Artifact without turning
producer data into HTML or script. The machine-readable v1 contract is
docs/contracts/renderable-artifact-v1.schema.json. It carries a deterministic
JSON payload, explicit renderer name, text fallback, security markings, source
cluster/dataset/job references, bounded source context, and a safe JSON download
filename.
Draft 2020-12 schema validation is necessary but not sufficient. Generic
validators ignore the contract's x-kamiwaza-* annotations, while Kaizen's
runtime also enforces canonical envelope and context sizes, JSON depth,
nonblank trimmed required text, canonical table-column keys and map-form
security-marking portion names, and uniqueness for those names. List-form
security markings trim each value before checking uniqueness. Run every
candidate through the shared runtime normalizer; docs/contracts/README.md lists
these runtime-only checks, and
Limits & quotas lists the fixed values.
payload contains a semantic type plus exactly one authorized value. Use
data for the complete governed JSON result. If duplicating that result is not
appropriate, use result_ref={id,content_digest,version?} instead. id is an
opaque server-side identifier, never a URL, and content_digest is the exact
sha256: binding. This release validates and preserves result_ref, but does
not resolve it. The UI uses the producer's text_fallback; the JSON view and
download retain the opaque reference. A future reviewed server-side resolver
must use the current member and room authority, fail closed when the result is
missing or its digest does not match, and never resubmit the source job or
reconstruct fields that the governed result omitted. The browser must not fetch
the reference directly.
security.markings accepts either a nonempty list or a nonempty map of portion
name to marking. Use the map when the governed result distinguishes a banner,
summary, rows, geometry, or other portions; do not flatten those values. A
producer-side markings_error is a hard failure: do not create the renderable
artifact and do not infer or default a replacement marking.
Provenance and security markings are immutable across Artifact revisions. If a new result has a different cluster/dataset/job binding, source context, or markings, create a new Artifact instead of updating the old one.
An instruction-only skill cannot author this envelope through the model-callable
create_artifact or update_artifact tools. Those tools reject the reserved
kamiwaza-renderable-artifact language so model output cannot fabricate
authoritative markings or provenance. The skill must call a reviewed producer
tool whose trusted result carries the complete envelope. An internal tool places it under
ToolResult.presentation.artifact_outputs[].envelope; it reaches the same
owner-scoped audited writer. A vendored package is not one of those producer
forms. The package boundary rebuilds every declared artifact from allowlisted
fields, so a package's envelope, its materialization_key, and a declared
kamiwaza-renderable-artifact language are dropped before the declaration
reaches the materializer — package output is saved as an ordinary artifact,
never as an attested one. A generic MCP producer declares
_meta["io.kamiwaza/artifact"]={"version":2,"source":"/<json-pointer>"},
where source selects the complete envelope from the successful result.
The shared runtime validates and canonicalizes either trusted form, establishes
a call-scoped server attestation that model arguments cannot set, and only then
invokes the existing writer. Direct model create/update attempts fail before a
version is written. Every declared durable output is included in the
materialization result, including invalid declarations and declarations beyond
the four-output persistence ceiling. Kaizen persists only the validated outputs,
strips every raw declaration from the returned tool result, and reports saved and
failed counts through the bounded _artifact_output notice. When any declaration
cannot be saved, the notice uses a server-owned failure instruction; a producer's
custom response instruction cannot conceal that failure or cause raw content to
be repeated in chat.
For the generic MCP form, administrator approval and registration of the server and tool—not schema validation—is the producer trust root. Kaizen's attestation means that output from that selected producer passed the envelope checks and the model could not substitute writer arguments. It does not independently verify the truth of the producer's source references, provenance, or markings. Review how the producer derives those fields before enabling it; a careless or compromised producer can mint false metadata that is still structurally valid, and operators must disable or revoke that producer when its integrity is in doubt.
Only the reviewed native table renderer is active in v1. Other names are
portable extension points, not executable authority: Kaizen displays
text_fallback and preserves the payload for JSON download until a reviewed UI
adapter supports that exact name. Never put credentials, bearer tokens, or an
unfiltered provider response in payload, result references, or provenance
context. Security markings describe handling; they do not grant access or
replace the member ACL.
An extension renderer claim is the exact triple
(renderer.type, payload.type, payload.version). Producers should emit a
payload.version integer from 1 through 65,535. Omit the field for an
unversioned payload; explicit null, booleans, fractions, and values outside
that range are invalid. Integral JSON numbers such as 1.0 normalize to an
integer before persistence and select the same renderer claim as 1.
An unversioned payload remains portable data but can
never select an installed pack. The envelope cannot name a module,
package, asset path, URL, or pack version.
Renderer code belongs in a separately versioned pack repository. Its backend
entry point exports host API version 1, its canonical pack id and version, the
ordered receipt claims, and raw plus normalized validators. Its browser ESM
exports the same identity and mountRendererV1({ root, envelope, assets, signal }). Browser assets are addressed only by logical id through
assets.get(id), which returns verified bytes and media type; renderer code
must not fetch a URL.
Each declared asset may be requested only once per renderer session. Cache and
reuse its returned bytes; a duplicate request terminates the renderer session.
Kaizen loads a pack only from immutable derived API and web images whose exact
install receipts match. The browser module runs in an opaque, authority-minimized
frame and receives only the validated envelope and declared bytes over a
private channel. On every Artifact detail read, the active backend pack must
readmit the exact stored content and bind that decision to its SHA-256 before
the browser may mount. Unknown or disabled claims, receipt skew, validation
failure, and renderer failure all preserve the Artifact and use text_fallback. See
Artifact renderer packs for the build,
review, rollout, security, and rollback contract.
When security.markings is portioned, banner is the exact top-level marking
owned by Kaizen's outer Artifact chrome. It must be present when the governed
result requires a top-level banner. Renderer code may show subordinate summary,
window, or geometry portions, but must not duplicate, translate, hide, or
replace the host banner.
Building a package
Packages bundle tools and may include third-party code.
The security property you inherit
Approved packaged tools run in the package-tool-runner: a separate, secretless process, deliberately without the tokens or keys the trusted backend holds. Proposal testing is a different phase: authored files are validated in the existing isolated sandbox worker before an administrator can approve them.
An active package Tool can also be attached to one Prompt step without adding the package to the stored Agent. The attachment records the active package UUID and exact Tool name. At execution, Kaizen reloads that pin, rejects disabled or missing packages, clamps the Tool to the step Agent's capability ceiling, checks name collisions, and uses the same posture-resolved package dispatcher. This path does not bypass package admission, the secretless runner, approval, collaboration classification, or audit.
An untrusted package that executes successfully still cannot reach anything sensitive, because there is nothing sensitive in its process to reach.
The runner also treats every package and materialized workspace filename as untrusted. Use relative paths without parent segments or control characters. Before a package resource is read, Kaizen proves lexical containment, opens it without following a final symlink, and rechecks the opened descriptor against the content-addressed package root. A malformed path or symlink escape is a terminal package-load error.
Design to that boundary. If your package seems to need a backend credential to work, the design is wrong — the capability belongs on the backend side of the callback, with the package asking for it.
Lifecycle
Members can draft, test, and submit package proposals. Administrators review, approve, upgrade, disable, and delete.
The review exists because packages carry code. Creating an agent for yourself needs no approval; shipping code that others will run does.
Package Creator keeps a draft recoverable across turns. Reload the proposal
before changing it and send its current optimistic version with every patch.
Manifest patches are closed and preserve omitted fields. Local-package file
patches operate on named relative POSIX paths, support preview before apply,
and validate the entire resulting tree atomically. The current limits are 64
files, 128 KiB per file, 1 MiB across the tree, and a required package.json.
Traversal, absolute paths, reserved roots, duplicate removals, and oversized
trees fail without changing the draft. Changing any byte clears stale test
evidence. A native Pi package also clears its receipt when any reviewed
manifest field, ref, security posture, or package format changes.
Shared lifecycle extensions are product code
Do not use a member-authored package extension to change the shared agent lifecycle. Member-authored extensions load only during disposable sandbox validation. At runtime, Kaizen composes their reviewed tools, skills, prompts, and themes through the existing package boundary. It does not activate their Pi lifecycle hooks in the trusted sidecar.
A shared lifecycle extension requires a source change under sidecar/pi. Add
one statically imported factory and one descriptor with an exact ID, version,
unique order, host-protocol range, and allowed hook set. Use only context,
before_provider_request, agent_start, agent_end, agent_settled,
turn_start, turn_end, tool_call, or tool_result. The registry loads the
complete enabled set into temporary state, inspects Pi's registrations, and
publishes one immutable chain only when every descriptor passes.
If the extension declares a tool, its descriptor must name an existing reviewed package identity. The declared name and parameter schema must match Python's frozen authorized tool snapshot exactly. The extension executor is discarded. The existing authenticated callback remains the only path to first-party execution or the secretless package runner.
Do not register commands, flags, shortcuts, renderers, provider adapters, headers, models, session actions, UI, shell commands, messages, journal entries, or direct tool execution. Add or change a hook only with shared-host tests that prove ordering, timeout, rollback, output validation, and identical behavior across every pipeline controller.
Native Pi packages
Set package_format to pi only for a locally authored package. This selects
Kaizen's native Pi authoring profile; it does not select a second runtime or
version. Package Creator accepts no Pi-version field. The sandbox image is
built from the same canonical Pi lock as Kaizen's Pi sidecar, and the validation
receipt records the runtime version that actually loaded the proposal.
The authored package.json must:
- use the same name and exact version as the proposal;
- include
pi-packageinkeywords; - declare resources under
pi.extensions,pi.skills,pi.prompts, orpi.themes, using package-relative files, directories, or globs; - put Pi host packages such as
@earendil-works/pi-coding-agentinpeerDependencieswith*, never in bundled dependencies; - exact-pin every ordinary dependency and development dependency; and
- omit install and publish lifecycle scripts. Dependency installation always
uses
--ignore-scriptsas a second boundary.
Kaizen composes only Pi tools, skills, prompts, and themes. Commands, flags, events, shortcuts, custom renderers, and other host hooks are unsupported. The real loader reports them and validation fails; omitting them from the reviewed manifest does not make them acceptable. Tool names and parameter schemas, and skill names and paths, must exactly match the surface Kaizen reviews. When a member explicitly names one of those authorized tools, the runtime gives that exact descriptor first-step visibility so the model can call it directly. The hint is intersected with the frozen run catalog and cannot grant an omitted or unauthorized package tool. An unambiguous imperative—“must call tool” or “use/call tool exactly once”—also binds that one already-authorized name into the signed Pi run policy. Substitutes and an early answer are held until the call is attempted; ordinary mentions, negative instructions, ambiguous multi-tool directives, and unauthorized names do not bind. Package authors do not implement this gate. Keep it in the shared runtime so package and first-party tools receive the same semantics and Python remains the execution authority.
test_package performs pure metadata, dependency, path, and declaration checks
before dispatch. A skill-only package goes directly through Pi's real skill
loader and does not need a fake Node test. A package with extensions must first
pass its real node:test suite. The worker then discards that test tree, creates
a fresh exact tree from the server-supplied files, and reinstalls dependencies
using the time remaining in the same validation deadline. Pi's extension loader
therefore cannot observe files rewritten by tests.
Authored extensions load in a disposable child process. The trusted validator parent imports no authored module; it accepts only a bounded child observation, resolves static resources itself, and owns the inherited descriptor carrying the final result. Do not make package validation depend on stdout or on mutations performed by a test. Both skill and extension paths execute in the deployment-selected sandbox worker; neither runs in the backend or falls back to the package runner.
The bounded result includes an artifact hash, reviewed-contract digest, observed runtime profile and version, whether Node tests ran, the discovered surface, unsupported registrations, and actionable diagnostics. Repair only the reported proposal fields or files, reload the current version, and retest. Submission and approval require a current passing receipt for the exact bytes and reviewed contract even when the general package scan policy is set to warn.
Testing is a property of a saved draft, not a step on the way to submitting
one. Package Creator runs test_package once a saved proposal has files, and
always when you ask it to test, so a draft you deliberately leave unsubmitted
still carries a receipt a reviewer can read. Asking for a package "without
submitting it" therefore still produces tested bytes.
Instruction-only packages
A package can contribute reviewed instructions without executable tools. Put
those instructions in top-level manifest skills; each authored skill has a
name, optional description and safe path, and a bounded non-empty body. That is
the runtime source composed into an agent's persona after approval.
metadata.pi_ui_resources.skills is only presentation metadata. It can make a
skill discoverable in the UI, but it is not a substitute for top-level
skills and does not activate instructions by itself. Package Creator mirrors
authored skill name, description, and path into that display projection while
keeping one shared runtime body.
For a native Pi instruction-only package, the authored skill file must be in
the pi.skills resource set and its body must exactly match the reviewed
top-level skill body. Pi discovery and Kaizen review must identify the same skill
name and path.
When another agent draft is waiting for the package, return the typed proposal handoff. Do not invent a package grant. Agent Builder persists the exact same-owner proposal as a dependency and resumes only after administrator approval produces an active package with the same name and ref.
Capability ceilings still apply
A package's tools are subject to the same two ceilings as any other: the agent's capability set, and the running member's permissions. A package cannot grant itself reach.
For delegated Tasks there are additional non-bypassable ceilings: the active automation grant, registered workload role/revision, Core run claim and fence, immutable authority deadline, protected-resource descriptor/guard, member and workload quota ledgers, exact approval, and current cancellation/readiness. These checks belong to the trusted backend callback. The secretless package runner receives only the bounded input and projection needed for approved package code; it never receives delegated or brokered authority material.
Each package tool also has a collaboration_class. Omit it, or set it to
unknown, when the tool must remain unavailable in durable shared rooms. The
package manifest accepts only actor_bound_read, actor_private_read,
actor_bound_write, and unknown. This is reviewed admission metadata, not a
field package code can return at runtime. Actor-bound classes keep execution on
the initiating participant; actor-private reads are still removed from shared
rooms. Packages cannot declare audience-intersection or conversation-resource
authority because those scopes require a Kaizen-owned adapter that can prove
the complete room audience.
Delegation and member capability selection can only narrow this surface. The request-scoped agent specification is intersected again when runtime package wrappers are built, so a stored registry grant cannot reintroduce a tool that a parent ceiling or the current selection removed.
MCP
Kaizen can record MCP tool descriptors on an agent package, but the current version does not host or execute them. They are declared for forward compatibility.
Do not build a package that depends on live MCP execution, and do not present MCP tools to members as working capability.
Testing
- Unit the tool's projection: given raw output, what does the model see?
- Boundary the registry: cardinality and closed-set membership.
- Integration the authority path: does an unauthorized call deny rather than degrade?
- Failure the failure mode: does a timeout return the original authorized result and continue the turn?
For a brokered Core operation, also validate the emitted OpenAPI document:
authentication metadata is present, operationId is stable and unique, schemas
are bounded, the tag is meaningful, and the effect annotation is honest.
Checklist
- Closed set updated and the boundary trip-wire updated.
- Kind declared honestly — anything persistent is
write. - Registration conditional; ungranted by default.
- Executes as the initiating member.
- Protected operations use a registered Core resource contract and guard.
- Exact effects are reserved and consumed under the current claim/fence.
- Returns a bounded, member-safe projection.
- Fails closed; never widens on failure.
- Anticipated failures return
outcome="error"rather than raising. -
timeout_sdeclared where the work legitimately runs long. - No model credential; no ambient identity; no bespoke audit path.
- Packaged code assumes a secretless process.
- Native Pi packages select
package_format=pi, not a Pi version. - Pi metadata, exact dependency pins, supported resources, Node tests where required, loader surface, and current validation receipt all pass.