Every release, cut in the open.
The canonical version history of @namzu/sdk and every first-party provider package — the kernel, providers, and the computer-use surface. Releases are cut from cogitave/namzu and refreshed hourly.
@namzu/sdk
17 releases
Minor Changes
- —
4a6ec17: Refuse to claim strict tool input generation where the wire would refuse the request.
editandwritesetenforceModelInput, and the Zen driver answered that withstrict: truefor both on the chat-class wires. The Zen free-tier gateway enforces the strict subset's structural rule itself — every object that declarespropertiesmust list all of them inrequired— and refuses the WHOLE request when one tool fails, soedit's optional fields (a find-and-replace OR an insertion) took every other tool in the call down with it.cat-jam-freeis the model that enforces; the symptom wasInvalid schema for function 'edit'.- —
@namzu/sdkgainsacceptsStrictSchema(schema), the wider question a driver asks before claimingstrict: trueon one request: the keyword deny-list thatfindStrictSchemaViolationsalready runs, plus the structural rules, plus a guard for a tool that declared no schema at all (the deny-list walks nothing inundefinedand finds nothing). It is additive;findStrictSchemaViolationsandassertStrictSchemakeep the registration-time job they had. - —
@namzu/zennow sendsstrict: acceptsStrictSchema(...)for a tool named inenforceToolInputSchema, sowritekeeps the guarantee andeditsendsstrict: false. Responses still sendsstrict: falsefor every tool, as it did. Nothing about a schema changes, and Namzu validates tool inputs before execution either way. - —
edit'sinsertLineunion branch{ const: 'end' }states notype, which the strict dialect has no spelling for; it is now{ type: 'string', const: 'end' }. The validator reports any union branch that never states a type.
What breaks. A caller that relied on Zen sending
strict: trueforeditloses constrained decoding for that one tool on the chat-class wires. To keep it, the schema would have to require every field and make the optional ones nullable, which changes whateditaccepts on every provider. - —
- —
Major Changes
- —
efb7b06: Extend the closed
AcpSessionUpdateoutput union withagent_thoughtreasoning boundaries andagent_messagecompleted messages. Exhaustive consumers must add handlers for both kinds before upgrading. Existing variants and required fields remain available. Reasoning boundaries contain no private replay payload; readable reasoning still arrives throughagent_thought_chunk.Message chunks carry optional actual message/turn identity and public commentary/final-answer metadata. Completed messages provide selected settled content and ordered public text parts.
turn_endedcan provide the authoritative result and its actual answer-message identity; consumers must replace the preview when a result is present, including an empty result that clears blocked output.Keep the coarse
AcpStopReasonunion and add optional exactreasonto terminal updates and prompt responses. Current runtime guardrails/refusals map torefused, resource limits tomax_turns, and parked segments retainreason: 'paused'under the older coarse cancellation label. Legacy aliases remain accepted. Prompt preparation cancellation returns an explicit cancellation reason even if no runtime event was produced. Streamed updates are sent in admission order and finish before the prompt response. Tool completion retains optional runtime-measured duration.Permission questions wait for already-admitted updates and fail closed when that context was not delivered. Update errors are caught immediately, retain the first failure and release the prompt slot after flushing; failed question sends settle their pending wait. Delivery failure does not undo runtime work already recorded.
Update the CLI session-isolation regression expectations to include the exact cancellation and completion reasons; the CLI runtime is unchanged.
- —
0f5aa96: Ready-computer Pal prompts now default to general application work guidance: understand references and acceptance criteria, make reversible changes, observe and correct the actual result, and validate saved files and requested exports before delivery. SDK hosts that need the previous prompt behavior can pass
workGuidance: 'basic'tobuildPalSystemPrompt. This guidance shapes model behavior; it does not certify semantic correctness.The SDK
readtool now refuses real PNG, JPEG and WebP bytes as text. Mount the optionalview_image/createViewImageToolfor saved-image inspection, or use a format-aware binary reader when pixels are not the desired result.getBuiltinTools()retains its previous tool roster. Saved artifact images provide no GUI screenshot authority.The CLI and desktop Pal runtime now explicitly mount
view_imageandimport_reference_images. Image import is a reviewed file-write operation using only the current operator input and admitted guest. Original image bytes and manifests survive durable review; current plan, pause and control guards remain in force. Ordinary chat and unavailable-computer sessions do not acquire guest tool access.The local Pal sandbox adds authenticated, acknowledged bounded byte reads. Rebuild the local Pal image from this release to inspect saved images; an older worker fails explicitly before file contents are requested. Existing whole-file requests and generic worker defaults are unchanged.
The Pal image also includes the standard
fileutility for format checks. This reports file type; it does not establish application or visual quality. - —
6ff961f: Add an owner-conversation source to the shared Pal input ledger, so an ordinary conversation (not a Pal) can leave a message in a Pal's durable inbox.
PalOperatorMessageBrokerandcreatePalOperatorMessagingTools(list_palsand an always-approvedsend_pal_messagethat takes onlypalIdandbody) are new; the host fixes the tenant and the executing call supplies the conversation, so the model cannot choose or forge the sender, and the source carries no Pal address. Acceptance is a receipt, never delivery or an answer, and the existing Pal-to-Pal rows, route hashes, IDs and receipt namespaces are unchanged.Why this is a major release: the exported unions
PalIngressIntent,PalIngressInboxMessage,PalIngressRouteKey,PalIngressAuthorizationRequestand theacceptIngress/readIngressresults gain anoperatormember (kind: 'operator', sourceoperator-conversation), andingressMessageRefcan return the newnamzu-pal-operator/1namespace. TypeScript code that narrows these unions withkind === 'observation' ? … : <channel fields>no longer compiles; handlekind === 'operator'(its source is{ kind: 'operator-conversation', tenantId, sessionId }) before reading channel fields. A customauthorizecallback must also return a decision for anoperatorrequest: allowacceptanddeliveronly for a host that has already shown the owner a review, and refusewakeunless the owner started the Pal. The publicRuntimeContextMessageKindunion is not widened: a delivery is recorded aspeer-message, and its rendered envelope names the owner's conversation and says it is untrusted context, not an approval.An older SDK process sharing a communication store that holds an owner-conversation message fails to read that recipient's record rather than overwriting it; stop older processes before sharing the path.
- —
53aed2a: Add a shared durable Pal input ledger for existing Pal messages, closed host activity facts and authenticated channel messages. Generic intake and finite dispatch retain exact original-log receipts, current authorization and one unresolved recipient claim across all sources. Existing Pal-only APIs, row shapes and message IDs remain supported; their writes preserve the new input families.
The public RuntimeContextMessageKind output union and RUNTIME_CONTEXT_MESSAGE_KINDS catalogue now include host-observation and channel-message. Consumers that exhaustively handle runtime context must add those cases when upgrading. Treat both as untrusted context; neither grants approval or operator authority. This output-type expansion requires a major SDK release even though the new intake APIs are additive.
Persisted communication and operation records upgrade to schema version 2. Stop older SDK processes before sharing these store paths; older binaries refuse the new records instead of overwriting mixed input state. Observation and channel messages remain untrusted runtime context, never operator approval or permission grants.
- —
f7f48f9: Add the explicit
cmdShellDialect and change the native Windows host-shell metadata default fromshtocmd. The physical command shell remains Node's platform shell (normally CMD); no PowerShell or WSL switch occurs. Update exhaustive dialect handling to includecmdand pass the actual value to authorization helpers.CMD lines are conservatively opaque until a matching parser exists. Command-specific allow rules and skill patterns no longer pre-approve native CMD commands. The existing unknown-program escalation still requires exact-call review, even when the gate has a whole-tool allowance. Use that review or explicitly select a POSIX interpreter rather than incorrectly labelling CMD as
sh. Sandboxed Linux guests retainsh.SDK query prompts disclose the active command tools' host or guest execution dialect, including minimal and cached prompts. Explicit Windows
bash.exeandsh.exeoverrides retain their matching POSIX dialects.Native Windows CLI model shell calls inherit the exact-call review requirement. The scheduled-run floor refuses CMD commands rather than checking them with POSIX quoting; confirmed script jobs still accept only explicit bash/sh interpreters. Use an installed POSIX interpreter where appropriate or run reviewed interactive CMD calls.
Minor Changes
- —
dd7ee4b: A failed model request on a turn that has no token limit anywhere above it no longer blocks that turn's next request. Before, an unanswered request (a 502, a dropped stream) left the account "in flight" and unresolved, so the same turn could never ask again and a host could only abandon it. The request still stays recorded as unknown spend, never as zero, and a late usage frame for it is still added.
A turn with any finite limit (its own, or on an account above it) behaves exactly as before: unknown spend still refuses the next request, because the unknown amount may already exceed the limit.
SessionTokenBudget.boundedreports which of the two an account is. If you depend on the old refusal for an unlimited turn, checksummary().unresolvedRequestsyourself; nothing else changes for limited turns. - —
4314f90: A tool call a person declined at review is now recorded as declined by them, with what they said, so a host that reopens the conversation can show it. Nothing you already send or receive changes shape, and no code needs to change to upgrade.
New and optional:
ToolReviewAnswer'sreject,AcpPermissionOutcome'srejectand thereject_toolsdecision takedeclined?: { note?: string }; an ACP client'srejectanswer takes the same field; and a genericToolCallViewresult carriesdeclined?: { note?: string }. A host that setsdeclinedon its reject gets, on that call'stool_completed, a view whoselabelis the call's target (path or command) and whosenoteis the person's words, cut at 4,000 characters. A host that does not set it, a call the authorization gate refused even when the person rejects the rest of its batch, and every other refusal by policy (strict or plan mode, no one to ask, the authorization gate, a repeated failure), is recorded exactly as before: an error result with no presentation. The text the model reads is unchanged.ToolExecutor.executeBatchtakes an optional fifth argument for the same purpose.The CLI's ACP server passes the field through, so a client such as Namzu Desktop that reports "the person said no" now shows "Declined" after a reload instead of "details unavailable". The terminal UI does not report it yet, so a call declined there still reloads as before.
- —
b8852a1:
abandonTurntakes a new option,withdrawMessages. Pass it when you give the turn's prompt back to a person to send again (a message box restored after "continue without this reply"): the abandoned turn then contributes no message to the next turn's context, so the provider receives the re-sent prompt once instead of twice. The records stay in the log; the closingturn_failedcarriesfailure.details.messagesWithdrawn: true, which the fold honours. Nothing changes unless you pass it: an abandoned turn's messages stay in the context by default.SessionLog.abandonTurnaccepts the same option as a fourth argument; a customSessionLogthat wraps another should forward it.The CLI's
namzu/sessions/abandon-pausedpasses it, which fixes Desktop's Continue without this reply sending the person's message to the provider twice. - —
7f5ac50: Expose explicit paused-turn recovery through optional
AcpAgentGateway.retryandACPServer.retrySession. Hosts retain the existing ordered update, review, cancellation and single active execution owner without inventing a new prompt.The desktop CLI host advertises scoped retry-status and retry methods. It resumes only the exact verified checkpoint of a classified retryable provider pause with resolved original accounting. Human decision holds and uncertain provider usage remain blocked, including unlimited turns; this feature never resets a ledger or abandons an active turn. Same-process retries preserve captured approval/effort and the recorded model. After reconnection, default Retry is unavailable when the original approval settings cannot be verified; a deliberate host retry must explicitly select a permission mode in ordinary conversations. Pal recovery also pins the authenticated original computer generation and environment identity before sending, and rechecks them at resumed provider/tool entries. A cold, originally offline, replaced or unavailable Pal computer lifetime cannot be substituted with the currently ready computer. Pal ownership and current computer-control guards remain required.
- —
56d88dc: Add optional
namzu/tasks/updateplanning notifications, exportedAcpTaskandAcpTaskUpdateshapes, andACP_TASK_CAPABILITY. Hosts enablesupportsTaskNotificationsand clients declarenamzu/tasks; existing core updates and default wire behavior remain unchanged. Notifications preserve planning IDs, failed status, dependency and owner clears, and deletion without forwarding private task descriptions or metadata. - —
3727e2b: Add the read-only
ACPServer.getSessionCwd(sessionId)lookup for the exact workspace of a session published on the current connection. Reserved loads, unknown identities and stopped servers returnundefined. Hosts can authorize new-session model preparation before the first durable turn without accepting an arbitrary client-provided session ID. Folder trust and durable/Pal ownership checks remain the host's responsibility. - —
84452ec: Add
ToolManager.prepareExecutionAsync(name, rawInput, signal?)and use it for direct, nested and structured tool calls. Asynchronous refinements and JSON-safe transforms now prepare one normalized input for authorization, review and execution. Execution retries reuse that input; actual rewrites are revalidated. The existing synchronous preparation API remains unchanged.Cancellation ends preparation without admitting a late result. Schema callbacks remain trusted host code: keep external work free of effects or cooperative with host cancellation. Non-JSON host inputs must not be mutated while preparation is pending.
Observation deduplication no longer replays historical schema callbacks. It uses current executor evidence tied to the actual successful call and exact result. Historical or recovered results without this proof remain full, so a new turn or resume may retain more context under the existing compaction limits.
- —
53aed2a: Add authenticated Pal channel ingress with captured host connections, a fresh actor per event, immutable native conversation targets, and shared durable inbox delivery. Recorded reply and action routing verifies the original owned conversation receipt and current authority; it does not send remote messages automatically. Hosts must provide a trusted event verifier and explicit receive, wake, reply and action policy. The CLI includes a private local HMAC fixture adapter and a bridge to exact native parked tool-review actions; other channel actions remain unsupported.
- —
6194bda: Observe a background server's output without waiting for it to exit.
BackgroundJobRegistry.waitForOutputand the optionalBackgroundJobRegistryRef.waitForOutputmatch a bounded UTF-8 literal on stdout, stderr or either pipe independently, preserve combined byte cursors, and report match, exit, stop, timeout or cancellation outcomes with explicit retention gaps.Set
output_containsand optionaloutput_streamonwait_for_jobfor one readiness observation. This mode never marks the process as work that must hold a finishing turn open. Omitting the condition preserves the existing exit wait. Custom hosts must implement the optional output-observation method to support readiness; unsupported hosts refuse it rather than waiting for exit. Observers have per-owner and registry limits and are cleaned up on every outcome. Process output remains evidence, not a guarantee of service health.Byte caps and cursors now skip partial UTF-8 code points and report those skipped bytes instead of introducing replacement characters into the retained output.
- —
301f5b3: Add opt-in
structuredResultSpilling: trueto built-in session logs. Large screened structured tool candidates and accepted final JSON can use separate checked spill references while their records remain within the 4 MiB ceiling. The default keeps existing inline records; each spilled JSON body is limited to 16 MiB. Live events and returned turns still carry the full value.Use
readStructuredOutputon a completed record from a verified log read to restore accepted JSON. Completed-call recovery hydrates only the latest selected completion and refuses missing or corrupt evidence without replay.readSpilland spill stores accept optional byte limits and cancellation; built-in bounded reads verify actual size, UTF-8, regular files and hashes. Custom backends remain responsible for their own I/O limits. This does not restore a pre-crash review decision or introduce general tool-data artifacts. - —
6194bda: Add
resumeSessionIdto local child-task admission and expose the admitted conversation asTaskHandle.childSessionId. A follow-up keeps the child's conversation and history while creating a fresh task, parent-turn attribution, cancellation channel, budget reservation and current configuration. Existing terminal task handles remain unchanged. Continuation requires a retained capability in the same manager and an unchanged registered definition; replayed history alone cannot authorize execution, and isolated workspaces are refused.Existing builders supplying a custom log for another session keep their first-invocation behavior. Such a log does not create continuation authority for the admitted child, so a later follow-up is refused instead of replaying unrelated history.
Add optional invocation identifiers to immutable summaries:
SessionSummaryRef.turnRef,SessionSummaryMaterializer.materialize({ turnId }), andSessionStore.getSummary(sessionId, tenantId, turnId). Built-in stores seal follow-up summaries independently without replacing the original conversation summary. Custom session stores enabling continuation must declaresupportsInvocationSummaries: trueand implement this optional invocation key; their existing calls without a turn id retain their behavior.Continuation also requires
supportsOwnerVersionCas: true: custom stores must honor the optional expected ownership version onupdateSessionandrecordSummary. Admission, rollback and pre-invocation checks refuse changed ownership, andmaterialize({ expectedOwnerVersion })prevents an old invocation from idling a new owner during completion. Built-in disk stores serialize these writes within a process; this does not establish a cross-process lease. Calls omitting the version preserve their existing behavior. - —
56d88dc: Add
DiskTaskStore.listStrict()for hosts replacing a complete task projection. It rejects unreadable directories and corrupt records rather than silently presenting an empty or partial list. A session without a task directory still returns an empty list; existinglist()retains its tolerant behavior. - —
f7f48f9: Add optional
terminate(signal?): Promise<void>toSandboxDetachedProcessand backgroundJobProcessfor providers that must confirm a remote process tree stopped. The registry waits for this confirmation before reporting termination. A failed confirmation rejects the stop call and retains the owned running job with optionalrecoveryRequiredand a safestopError, so the host can retry recovery without forgetting live work. Providers that use the existing synchronouskillmethod retain their current behavior. - —
9ef6506: Expose live tool progress and actual turn failure messages through optional fields on existing ACP update variants. Add explicit optional
namzu/*ACP host extensions and advertise their method names. Gateway history loading now receives the requested workspace as an optional second argument; existing one-argument gateways remain compatible.The CLI now loads durable, project/tenant-scoped ACP conversations and refuses archived writers.
namzu acp --desktopenables operator methods for folder trust, scoped conversation/history, safe provider metadata and background jobs. Ordinary ACP connections retain the core method set. The private desktop preview uses this existing runtime and log rather than a second conversation store. - —
56e846c: Add optional inline user attachments to ACP prompt requests. Hosts opt into delivery with
supportsPromptAttachments: true; initialization then advertisespromptAttachments: true. Clients can check this capability before sending bytes, and gateways receive the validated attachment payload alongside the prompt. Stored attachment references are refused across this boundary; inline payloads are limited to eight attachments and 3 MiB of decoded bytes per message.The CLI opts in and preserves image/document attachments in the actual user message and settled conversation history. Existing plain text requests keep their behavior. The private desktop admits native file picks or dropped/pasted bytes as bounded images and UTF-8 text, keeps draft and queue ownership, and refuses image submission to an older CLI rather than silently dropping it.
New image/document inputs are refused before send when the selected live provider explicitly declares that it cannot receive them. Desktop files remain available for retry with a suitable model. Attachment/settings-only drafts survive a connection rebuild, and queue editing restores captured options together with its text and files. Pre-turn CLI errors retain their actual diagnostic rather than becoming a generic failed turn.
ACP also accepts optional
AcpPromptOptionswith a reasoning effort and tool review mode. Hosts opt in withsupportsPromptOptions: true, advertised aspromptOptions: true; unsupported or malformed explicit settings are rejected before a turn starts. The CLI validates reasoning effort against the actual selected runtime's supported menu and applies the captured review mode to that turn. - —
2057fd6: Surface provider-hosted web search and fetch activity through existing ACP tool updates, using qualified IDs and only provider-reported state and bounded public captions. These display receipts do not grant local execution or approval.
Desktop conversation history adds optional validated journal message IDs and timestamps for messages and work, and restores saved hosted search receipts. Missing timing and unfinished operations remain explicit; reopening history does not invent a current clock or a successful outcome. Existing message text and execution authority are retained.
- —
6f46da3: Add an optional
durableInboundsource to query, runAgent and query-backed agent configuration. Deliveries use ordinary session-log message records and await both their append queue and the host's exact acknowledgement before inference. An explicit sessionLog is required for this optional source.Export durable claim/reference/receipt contracts and bounded reference validation. Runtime context can retain an optional delivery reference without acquiring operator authority. Existing initial input, synchronous queues and outstanding-work holds retain their defaults.
- —
be9f8b6: Add opt-in
structuredOutput.toolResultRetention: 'durable'for tool mode. Retain screened, post-hook JSON independently of capped tool previews, so larger valid candidates can reach review and final structured settlement. The defaultreceiptbehavior is unchanged; native mode rejects the durable tool option.The full JSON is an additional host-visible
tool_completed.structuredResultJsonfield and a verified completion recovery field, not provider-message content or proof of acceptance. Guardrails, hooks, skips, cancellation and the session record size ceiling still apply. Resume preserves execution evidence without automatically accepting a pre-crash candidate or replaying its tool. - —
24cb6b4: Add
createHarnessSessionand host-composed external engine contracts. External sessions record an immutable engine/profile/native-session/cwd binding, authored prompts before dispatch, native item identities and exact review decisions under the existing session writer lease. Admission is required before native execution or approval; interrupted connections require matching native history and never automatically resend prompts. Existing Namzu query, provider and tool execution APIs and defaults are unchanged. New optionalharnesspayloads use the existing session record kinds.New external-bound journals cannot be continued through kernel
query,resumeSessionorTurnRecorder.open; use their recorded harness adapter or a separate Namzu session. Reading their owned history remains available. - —
d5030ca: On Windows,
bashwithrun_in_backgroundnow runs the command: before, the job registry spawned/bin/sh, which does not exist there, so every background job ended within milliseconds with no output and no explanation. Background jobs, foregroundbashcalls and plugin shell hooks now share one spawn (spawnHostShell, newly exported). A command that cannot be started is reported as "Could not start the command: <reason>" (and asfailed to startbyjobandwait_for_job, with anerrorfield) rather than as a plain exit, and Windows console output is decoded in its OEM code page instead of garbled as UTF-8. On Windows the command now runs under UTF-8 (an outercmdsets code page 65001 and starts the real one with the command in the environment), so letters the OEM page lacks, such asğandş, are no longer flattened togands, and a missing working directory is reported as such. Nothing to change on upgrade;BackgroundJob,BackgroundJobOutputand the job tool results gain an optionalerror. - —
6f46da3: Add durable Pal addresses, owned conversation routes, incoming message storage, captured senders, explicit directed consent and executor-correlated messaging tools. Acceptance is separate from verified session-log delivery and recipient execution; unresolved claims are retained for recovery without repeating effects.
CLI and desktop Pal sessions use the same SDK messaging tools and durable query input. Add
pal grant/revoke/inbox/dispatch: wake consent is explicit and finite dispatch uses preapproved tools. No external transport, automatic listener or Pal Team membership is installed by this feature. - —
56d88dc: Add
CompletionInbox.deferDelivery(taskId, settlement)anddrainAsync(signal?)so hosts can await an owned task's tracking writes before delivering its result. The existing synchronousdrain()keeps pending deferred results queued and reports rejected writes without consuming results. Cancelling an asynchronous drain releases its waiter while retaining the tracking promise and result. Query notification waits respect the caller's existing cancellation/deadline, and finalizers and outstanding-work holds never wait again on pending tracking.Coordinator background workers and abandoned foreground waits now settle their linked planning task and original approved plan step from the actual worker outcome exactly once. Completion notifications and explicit waits await that settlement. Replacing the active plan cannot redirect an old worker's outcome to a reused step ID. Direct background launches without a completion channel are refused before starting a worker.
Rejected scheduler admission also fails the linked planning record and original plan step rather than leaving them running. The original admission error is retained, an uncertain worker dispatch is not represented as absent, and failed tracking persistence reports both errors without retrying the worker.
- —
7f5ac50: Add optional
MCPClientConfig.transportFactoryfor caller-owned transports, preserving built-in transport configurations and the existing MCP protocol lifecycle without launching a host subprocess as fallback.Add optional
Sandbox.openStdiowith allocation-owned interactive guest pipes, byte streaming, confirmed process-group shutdown and explicit per-request device operation barriers. Idle services permit operator takeover; an unknown issued application operation permanently fences the allocation until computer stop and cannot be cleared by a late response or closing an external application's MCP server. The local Pal worker must be rebuilt alongside the runtime to advertise interactive support. Older workers refuse this capability before spawning; ordinary foreground, background, file and desktop APIs remain available.Keep quiet interactive services alive with bounded worker heartbeat frames, discarded by the matching runtime before application output and without changing request ownership or uncertain-effect barriers. Rebuild the worker and load the matching runtime together for the interactive protocol.
- —
6f46da3: Add
PalRuntime.onLifecyclefor live computer and admission observations, with unsubscribe and isolated listener failures. AddcreatePalActivitySourcefor authorized, bounded pages of approved metadata from an owned original Pal journal. No private message, tool argument, result or error body is exposed in activity facts.Activity cursors must remain unchanged previously emitted outputs stored by a trusted host. Their scope hash binds ownership but does not authenticate a client cursor, and previously consumed prefixes are not rescanned. Hosts must resolve stored cursors rather than accept model, renderer or remote anchors directly.
These observation APIs do not grant execution, approve actions or wake Pals.
- —
5e2fb3a:
DiskPalActivitySubscriptionStore.list()is an additive trusted-host read of frozen latest subscription records, including disabled subscriptions. A missing root returns an empty list, while malformed or unreadable committed records and aliased directories reject the complete projection. Hosts must still enforce tenant and participant access before exposing metadata.Custom
PalActivitySubscriptionStoreimplementations do not acquire a new required method, and existing create, get, consent and progress behavior is unchanged. - —
84a9472: Add optional provider-owned Pal computer takeover through
PalComputerControl,PalComputerInputand runtime control methods. Hosts can transfer an idle guest to human input using its exact generation, return it without waking the Pal, and observe current control authority. Providers without the optional port retain normal Pal execution and explicitly refuse takeover.The runtime serializes control operations, refuses new Pal admissions during operator control, and requires each subsequent admission to obtain its own fresh screenshot before GUI input. CLI desktop ACP exposes take-over, return and bounded input methods; optional screen generation pinning rejects stale captures.
- —
c1027fb: Add a connected computer observation to
PalSystemPromptOptions.computer, with the current control mode, so hosts can describe an existing device independently of permission to execute tools.Fix Pal chat composition when the operator holds its connected computer. The Pal now receives its actual connected/control state instead of being told the computer is unavailable. Operator-held chat retains zero guest tools and performs no guest inspection or allocation; Return control admits the existing guest capabilities on the following turn.
- —
d8d562e: Add
buildPalSystemPromptandPalSystemPromptOptionsfor hosts to compose a Pal's saved identity, conversational language, public output discipline and truthful computer availability. The helper does not acquire execution authority. CLI Pal sessions use these shared instructions while retaining their pinned model and execution policy. Authenticated name and appearance edits update the next turn's display identity without rewriting the original introduction.Add
PalRuntime.admitConversation,PalConversationAdmissionandPalRuntime.computerChanging. Hosts can admit exclusive model-only chat without starting a computer, with current pause, cancellation and ownership checks. Guest acquisition remains separate and requires the real guarded environment; the existing mandatoryadmitcontract is unchanged. CLI manual chat supplies zero tools while offline or operator-controlled, then adopts the actual guest toolset on a later turn after explicit startup or returned control. Directed dispatch and checkpoint resume continue to require guest admission. The standalonepal chatcommand keeps its existing computer-startup preflight.Add
palConversationGreetingandPalConversationGreetingto reconstruct a stable English onboarding prelude from an owned conversation's original profile revision. CLI Pal claim and list results expose this separate host-authored intro without starting a model request or manufacturing a journal turn. Pal desktop history omits explicit assistant commentary and tool-call narration from its public display projection while retaining the original journal. - —
53aed2a: Add durable subscriptions for closed Pal activity metadata from an exact owned original conversation. Observation, disclosure, recipient receipt and idle wake have independent current permission checks. Custom publication authorizers must also implement the final accept phase requiring current observation, disclosure and receipt together. Accepted facts enter the shared Pal inbox before cursor progress; retries preserve identity and verified observation trails suppress feedback.
The CLI adds finite pal subscribe, subscription, activity and unsubscribe commands. Publication performs no inference or guest startup; explicit dispatch still requires current wake/tool authorization. Original turn intake and exact recorded delivery receipts are required for causality, and unknown evidence or exhausted journal read budgets reject without advancing progress.
- —
ce52b90: Add optional host-only
PalComputerScreenStream,PalEnvironmentLease.screenStreamandPalRuntime.computerScreenStream(palId, generation)for observing an exact current Pal computer over read-only RFB. Existing providers remain compatible; providers without the optional capability explicitly refuse live observation. Keep allocation authorization private in the embedding host and enforce read-only observation server-side; the stream does not grant operator input authority.The CLI exposes the owning desktop ACP stream method with geometry and generation rechecks, and reuses initialized registry roots to avoid repeated Windows ACL subprocesses while retaining fresh Pal definitions and directory identity checks. The local computer image adds x11vnc and authenticated binary WebSocket transport. Rebuild the installed image explicitly and restart the owning computer to enable live observation; older images retain screenshot/input behavior and advertise no stream capability. Persistent workspace and browser profile volumes survive.
The guest desktop includes a themed wallpaper, a real dock, Files, terminal and browser launchers, plus a local browser home page. Normal browser-window closure keeps the desktop alive; owned profile shutdown retains browser flush ordering.
- —
815433f: Add
DiskPalStore.delete(id, expectedRevision)and the desktop host'snamzu/pals/deletemetadata endpoint. Deletion uses a terminal immutable revision, hides the current Pal, and refuses future updates and execution admission while preserving historical profiles, journals, host files, and guest data volumes. Hosts must stop active work and confirm computer cleanup before deleting its identity. Existing customPalStoreimplementations require no new method. Readers from older releases do not understand terminal deleted profiles and should not be used to read a registry after deletion has been published.Add the desktop host's
namzu/conversations/archiveendpoint. It performs a scoped, writer-gated soft removal while retaining the durable conversation and files. Successful retries returnarchived: true; a physically absent strict journal returnsarchived: false, missing: true, never claiming that an absent conversation was archived. Hosts may discard only their own known unsent local projection from this missing receipt. Existing terminal archive commands are unchanged.Release a connection's exact idle native-engine writer before desktop archival, reserving the conversation until the operation settles. Active or unresolved work is refused and a failed native close retains cleanup authority for retry. The runtime reservation method is optional for existing embeddings.
Keep a Pal's approved stored control-path spelling when Windows native path resolution changes only its case, after verifying the same physical directory. Physical project identity remains canonical, while existing Pal profile revisions and journal ownership paths remain unchanged. Different directory objects and non-case aliases are refused.
- —
4367edc: Add opt-in held keyboard input for a Pal's operator-controlled guest. Providers advertise
heldKeyboard: trueonly after negotiating the owned guest's support;PalComputerInputthen accepts session-scopedkey_down,key_upandrelease_keys. Providers must release their tracked held keys before confirming returned Pal authority. Existing completekeytaps and AIcomputer_useactions keep their behavior, and older local images do not advertise holds.The desktop captures a new keyboard lifetime for each focus session, forwards press and release events without replaying browser key repeats, and releases only that lifetime on focus/view loss. Cleanup retains exact Pal/generation and operator authority checks and cannot release another lifetime's keys. Rebuild the local Pal image to enable this capability; a runtime update alone retains the older image's tap input.
- —
6f46da3: Add optional
PalAppearancepreferences to saved Pal definitions and create/update inputs. Character and color selections persist in immutable profile revisions; existing records retain their absent appearance and hosts may choose their own display default.The CLI accepts
--appearance <character>/<color>onpal createandpal update, and the desktop ACP create/update extensions carry the same validated preference. Supported characters arepixel,sprout, andspark; supported colors aregreen,blue,amber,violet, androse. - —
f7f48f9: Add persistent Pal identities, immutable profile revisions and an SDK runtime that requires an explicitly provided virtual computer before execution. Hosts can retain one warm computer per Pal, serialize active conversations, enforce current pause state and retry failed computer cleanup without losing ownership.
Add
namzu pal list/create/show/update/pause/resume/chatand desktop host Pal methods. Pal conversations pin a saved profile and bind exactly one Pal in their session log. File, shell and computer tools run in the Pal guest, with no host folder, host browser or host plugin fallback. The local computer image and engine must be set up explicitly before chat can execute. - —
846557b: Add
dryRunEdit(content, input)and theAcpFileChangePreviewtype, and let each tool call in asession/request_permissionrequest carry an optionalpreview({ path, before, after }) that the bridge forwards untouched.dryRunEditruns theedittool's own schema and apply code (single replacement,replace_all,edits[],insertLine) on a string, so a host can show exactly what an approved edit will write without touching the file.resolveWithinAnyRealis now exported besideresolveWithinReal. Nothing existing changes: a client that ignorespreviewand an agent that never sets it behave as before. - —
56d88dc: Allow
task_updateto mark a planning taskfailed, matching the existing TaskStore status. Addstats.failedtotask_listand show failures separately in model receipts and operator presentation. Failed blockers stop waiting without being counted as completed; earlier terminal tasks remain excluded from later-turn planning views. - —
e447a47:
tool_completedgains an optionalsavedPresentation: one label line (at most 200 characters) or a command's first line, never the output, journaled for every call that has no diff, and for an external engine's tools from a name map. Nothing existing changes:presentationkeeps its diff and cancelled/declined shapes, and live hosts that ignore the new field behave as before. A host that replays history can readpresentation ?? savedPresentationso a reopened conversation names its actions instead of showing a placeholder. The desktop host does. - —
57c99b2: Scheduled script jobs can opt out of a project folder with
workspace: none(--workspace nonein the CLI). Namzu creates a private working directory only after operator confirmation, and a script never loads that directory as a project configuration.Interactive schedule proposals now bind noteworthy results to the exact source conversation. The CLI stores them as durable host notices and displays them on resume without adding a fake model turn or changing the model's conversation history. An optional JSON script report lets a polling script distinguish a quiet check from a change and carry a bounded scheduler-owned state value between runs.
The CLI default for newly confirmed interactive proposals changes from detached results to source-conversation delivery. An unavailable source keeps its result pending and holds later runs until delivery succeeds. Existing jobs retain their previous behavior. To keep a new job detached, review
namzu schedule edit <job> --delivery none; the operator must confirm any exact pending results being waived. New workspace, source-delivery and report fields use job format v3, which older CLIs refuse rather than silently ignore. Upgrade the scheduler service together with the CLI. - —
c590142: New scheduled agent phases now default to unlimited tokens (
tokenBudget: 0) instead of 500,000 tokens. A positive request or configured limit still applies, and existing saved jobs retain their confirmed values. Setlimits.tokenBudgetto500000or pass--token-budget 500000to retain the old default for new jobs. To remove an existing job's limit, runnamzu schedule edit <name> --token-budget 0and confirm the edit. Iteration and timeout defaults remain unchanged.The SDK schedule tool accepts zero for an unlimited agent token allowance. Unlimited previews omit the numeric daily ceiling and identify the absence of a token limit; usage accounting continues to measure actual tokens.
- —
1fa60b9: The ACP bridge now sends a
provider_retrysession update when a model call fails transiently and will be tried again:attempt,maxRetries,delayMs, an optional HTTPstatus,serverDirectedand theturnId. It carries no provider body, header or credential. A client can show "waiting, retrying in 6 seconds" instead of a silent spinner.AcpSessionUpdategains this member. A client that already ignores update kinds it does not know needs no change; a client with an exhaustiveswitchoverupdate.kindshould add aprovider_retrycase (or a default branch) before it upgrades the type. - —
5a0a1f7: Expose optional
skipped: truemetadata on pre-tool hook completions, recovered completed-call records and step tool results. This distinguishes a non-error skip receipt from output produced by executing a tool. Existing records without the field remain unchanged.A skipped standalone structured-output or terminal tool now returns its receipt to the model instead of failing JSON integrity checks or settling with an unexecuted answer. Skips do not consume structured schema corrections; cancellation and iteration limits still apply. The runtime creates the marker only for pre-tool hook skips and does not infer it from receipt text or tool-authored result fields.
- —
e447a47:
task_createdandtask_updatedsession events, and the ACPAcpTaskrow, gain an optionalactiveForm(what the task reads as while it is being worked on, such as "Running the tests"). It is absent when the model gave none, so nothing existing changes; a host that ignores it behaves as before. A host that draws a live plan can show it beside the task in progress. The desktop host passes it on when it lists a session's tasks. - —
bebe135: A tool result can now say what the call did as typed fields, so a host builds what a person sees from fields and not from the model's text. Nothing you already send or receive changes shape, the text the model reads is unchanged, and no code needs to change to upgrade.
New and optional:
ToolResult.facts, a closed union (ToolFacts) of a command's exit code and its stdout and stderr as head-and-tail windows (ToolTextWindow) or a note that the output was binary, a read's path and size, a search's and a listing's counts, whether a write created, updated or left a file, and the kind of a failure (ToolFailureReason). The built-inbash,read,grep,glob,editandwritetools set it,tool_completedand the ACPtool_callupdate carry it, a genericToolCallViewmay carryfactsfor what the call is about, andadmitToolFactsreads a saved one back. A call refused without running, an unknown tool and a thrown filesystem error also carry afailurefact.execHostShelladditionally returnsstdoutBytesandstderrBytes. A post-tool hook that replaces the output also dropsfacts, so a redaction is never undone.If you build a view of tool output yourself, read these fields instead of the
STDOUT:andSTDERR:labels, the "Found 2 match(es)" and "1 replacement(s)" sentences and the notes about withheld credentials or shortened output, which are the model's interface and are not stable text to parse.The Desktop app now words every tool row from those fields (Read a.ts, Searched for export, Ran npm test · failed (exit 3), Couldn't read missing.txt: file not found), draws a command's colours, folds long output to its first and last lines with Show more, says "Binary output, 3 KB" instead of printing the bytes, counts a finished edit in the reply's changes card, shows paths in approvals relative to the project, and leaves "0s" off work that took under a second. The CLI host restores a saved conversation's
facts(without a command's output) so a reopened row reads the same.
Patch Changes
- —
7f5ac50: Preserve the recorded failure explanation in ACP
turn_ended.errorwhen a provider fault pauses a turn at a checkpoint. Hosts can display the actual failure instead of silently showing only a stopped turn. The existing coarsecancelledcategory and exactpausedreason remain compatible; ordinary review pauses and user cancellation do not produce an error. - —
1af4531: Preserve the runtime's completed tool presentation in ACP updates. File diffs and terminal results now reach ACP clients with the real result data instead of falling back to a generic text label. Older event producers without a presentation retain the existing fallback.
- —
c8613e0: Make the held sandbox acquisition timeout regression deterministic by controlling its clock after sandbox creation starts. Runtime cancellation and timeout behavior are unchanged.
- —
c1027fb: Enforce the existing tool-mode structured-output retry allowance for unrepaired invalid JSON, truncated arguments and schema mismatches, as well as missing output. All such responses share one counter; multiple invalid calls in a response consume one correction after every sibling result is recorded. The default remains two correction opportunities. Valid paired candidates, ordinary tool work, successful local repairs, host refusals and reviewer rejections retain their separate behavior.
Persist corrections in optional
Checkpoint.review.toolStructuredAttempts, saving feedback and answered results before another request. Exhausted resumes stop without another model call, and completed argument-failure evidence survives pending-batch recovery without revalidation. Old checkpoints and tool records remain readable without rewriting their bytes or hashes. Hosts must continue supplying the output configuration when resuming. - —
b6ea5ca: Models work on first open with nothing to set up. With nothing saved, Namzu Desktop and the terminal app now start on what they find, in this order: a sign-in Namzu already reuses (Claude Code, Codex), an API key in the environment, a local server (Ollama on port 11434, LM Studio on port 1234; each probe waits at most 300 ms and they run together), then the keyless Zen free models. They say what they picked in plain words ("Using your Claude sign-in, found on this computer.") and save nothing, so a key or sign-in added later wins on the next open.
/modeland the model picker change it.What changes for you: in the terminal app a first run with an API key or a local server in the environment no longer stops at the provider picker; it starts on that provider. The picker still opens when nothing is available. In Desktop the keyless Zen free models now count as usable, as they already did in the terminal app, so a new person's message box is ready and Send works at once; the "Add an API key or sign in to start" card now appears only when nothing at all is available. To keep the old terminal behaviour of choosing yourself, save a choice once with
/model.When Zen declines a free request that carried no key, both apps now say "Zen didn’t accept the free model right now. Try again in a while, or choose another provider. A Zen API key also works." and keep the technical detail under Details (Desktop) or
--debug(terminal). The genericprovider.permissionexplanation in@namzu/sdkno longer says the key authenticated: a 403 only says the request was refused. - —
bb0ad2e: Fail structured tool output explicitly when its retained receipt is no longer valid JSON, including without a reviewer, instead of publishing truncated or rewritten text as a successful structured result. Increase
maxToolOutputCharsfor larger tool results or select native structured output with a capable provider.Make
wait_for_taskreport the underlying turn outcome and retain failure explanations even with an empty answer. Completed scheduler lifecycles containing failed, partial or cancelled turns no longer appear successful; legacy completed results without a turn status retain their existing behavior. Blockingcreate_taskand linked planning failure notes also preserve those explanations and describe empty legacy completions accurately.Report a planning-task status change that cannot be confirmed as failure with the requested and returned statuses. The store may have refused the request or a concurrent update may have advanced it; other requested edits may already have applied. Existing store transition policies remain unchanged; successful task updates retain their quiet presentation.
- —
7f5ac50: Recognize a source-exact bare
[POSIX test command as a literal program instead of falsely classifying it as a glob-expanded command. This removes unnecessary unknown-program approval prompts for ordinary shell predicates and copy loops in automatically approved Pal guest work. Real bracket patterns, runtime-produced program names, opaque shell constructs and dangerous-command denials retain their existing review/refusal behavior. - —
7473449: Interactive terminals in the same project can discover and message one another with
/peers,list_sessionsandsend_session_message. Idle terminals can start a turn for peer context; busy terminals receive it at the next safe request boundary. Peer input remains separate from operator instructions and cannot grant approval. Different permission modes are refused. Use/peers offto refuse new mail and pause delivery in this terminal.The mailbox is bounded and process-local; queued is an acceptance receipt, not proof of model delivery. Conversation switches cannot redirect pending mail. Child-task activity now labels accepted messages as queued, and SDK manager documentation describes the inbound callback that query-backed agents actually consume. The SDK peer envelope refers to the host's reply tool rather than prescribing a CLI child-task tool.
- —
6194bda: Forward tool starts and progress through the query event stream while an approved batch is still running. Previously these events were recorded immediately but reached the operator only after the batch finished, leaving long commands and readiness waits without a live tool row. Event-driven observation retains the existing authorization, result ordering and provider-valid batch boundaries.
Closing the stream during a live tool event cancels the captured batch and waits for executor settlement before releasing the recorder and borrowed turn resources. Tools must still honor their cancellation signal; this does not promise forced termination of arbitrary custom code.
- —
f4b7d1d: Honor MCP input schemas that explicitly declare dictionary values through schema-valued
additionalProperties, including the empty{}value schema. Previously local validation silently removed those dictionary keys before dispatch, so application input could report success with empty event data. Retain declared keys, validate typed values and bound recursive dictionaries. Existing omitted/false closed-object behavior, explicit true passthrough, server trust, result provenance and execution guards remain unchanged. - —
6ff961f: A tool call made from inside another tool (for example through
run_code) to a tool that declaresrequiresApprovalfor that input is now refused and audited, even when an allow rule covers it. An approval cannot be requested from inside another tool; call such a tool directly. Hosts that mountedrun_codewith an allow rule on an approval-gated tool will see it refused. - —
023224a: A failed OpenAI or Google request now reaches the host with the provider's own type, code, parameter and request ID, as an Anthropic failure already did, so Details read "Status: 403 · Provider said: “…” · Type: invalidrequest_error · Code: model_not_found" with "Request ID: …" on its own line. The error's
detailfor a vendor SDK error whose message is only a sentence is now a JSON body of those fields ({"error":{"message","type","code","param"},"request_id"}) instead of the bare sentence; readproviderCodeor theDetailsof a host rather than matching the sentence text. The Google driver now reads its refusal body and request-ID header. The vendor project line in Details reads "Project: proj…". - —
12b5af9: Compare actual error/output/content fingerprints before refusing identical failed calls. Different errors restart the streak, and a successful potentially mutating execution allows a new check after repair. Denied calls and unrelated reads cannot reset the guard. Repetition advice no longer claims an unobserved future result is certain.
- —
093d657: A saved engine tool named
wait,wait_agentorwaitagentnow reads "Waited for agent", andclose_agent,interrupt_agentorkill_agent(any casing, with or without the underscore) read "Stopped agent", where they read "Used wait agent" and so on before. No export or type changes; a consumer that matched on the old label text should match the new one. - —
f0aeb9b: The package page (README) of
@namzu/sdknow explains the install, a first agent that needs no key, tools, a real model driver and the subsystem map. Only the README changed; no code, API or behaviour changed. - —
3727e2b: Keep an assistant message's runtime stream ID when recording its durable history and final settlement, including forced closing summaries. ACP clients can now reconcile the settled answer with its streamed message instead of displaying it twice. Distinct messages with identical text retain separate identities; caller-authored message IDs are not trusted as stream identities.
- —
56d88dc: Creating a task with
blockedByand deleting a task now emittask.updatedfor each existing task whose reciprocal dependency list actually changed, beforetask.createdortask.deleted. Disk and in-memory task stores keep live event projections aligned with their dependency records, without announcing unchanged rows or the deleted task's self-edge as a surviving task. Repeated blocker references no longer add duplicate reciprocal edges in the in-memory store; suppliedblockedByreferences remain intact.Confirmed disk neighbor writes are announced immediately, even when a later neighbor write, new-task record write, or target-file deletion fails. No created or deleted event is emitted for an unsuccessful creation or deletion, and already persisted changes remain observable.
- —
98e8469: Clarify that task_list returns open tasks and tasks closed in the current turn, with earlier completed or failed tasks retained in storage but omitted. Its output now reports retained omissions and distinguishes a filtered empty view from finding no records; the human presenter also qualifies its counts. Task selection, input and data.stats remain unchanged. Hosts needing all durable records should read their authorized TaskStore rather than infer deletion from this filtered tool result.
- —
Major Changes
- —
b7d83ec:
ToolManager.toPromptSection()no longer includes server-authored description hints for deferred MCP tools. It still lists their names. If a host calls this method directly and relied on those hints in its system prompt, calltoUntrustedDeferredContext()separately and put its labelled output in a request-only context message after history. The SDK query path does this automatically, bounds the hint list to 4,000 characters plus its provenance frame, prices the largest permitted rendering before step preparation and filters it to permitted tools. Hosts preparing their own steps can callsnapshotUntrustedDeferredContext()for the same bounded snapshot. MCP tools remain searchable by their full descriptions throughsearch_tools. - —
b7d83ec: Plugin
user_prompt_submitannotations now reach the model as user-role request context instead of system instructions. The CLI gives operator shell output and idle background-job exits the same request-context role. Hosts that used these observation channels to issue policy must move that policy to an explicit trusted instruction; the observations remain visible to the model on the current request.The
namzu mcp testHTTP 401 hint now uses a literal<name>placeholder instead of embedding the configured server name in a runnable shell command. - —
b7d83ec: Memory and saved resident state now reach the model as request-only user-role context rather than system guidance. In the SDK,
createMemoryRecallStepreturnsPrepareStepResult.contextinstead ofsystem; hosts that read or compose the hook result must usecontextand pass it through the normal preparation chain. Resident continuation, wake evidence, skill catalogues and learned text also move from systemdynamicorturncontributions tocontext; hosts that inspect placements must migrate.contextcontributions now render once beforeprepareStepso their estimated cost reduces preparation headroom; hosts relying on a contribution to observe preparation side effects in the same model step must move that logic to a later stage. The CLI moves file-backedUSER.mdandMEMORY.md, its stored-memory index, and automatic recall out of system prompts on new sends, resident turns and resumes. Interactive resident turns now place admitted data in the newSendOptions.extraContextrequest-only field; their fixed doctrine remains inextraSystem. Hosts that relied on memory files or resident data as system instructions must pass trusted guidance explicitly. This changes default provider-visible roles and may change model behavior and prompt-cache accounting; request-only context still uses input tokens on each request and the CLI bounds memory against estimated remaining request room. - —
b7d83ec: Plugin MCP server declarations now reject fields they do not implement, including
url,headers,inheritEnvandrequireApproval. Those fields were previously accepted and silently removed while the declared stdio command still started. Remove unsupported fields fromplugin.jsonor an in-code plugin; configure a remote server through the host's MCP settings and express approval in host source permissions. Plugin MCP startup now has a ten-second deadline across connection and discovery, configurable withconnectTimeoutMsup to one hour, and cleans up a failed connection before rolling back the plugin.Hosts supplying
ConfigRegistrymust move plugin reconnect-policy overrides frommcp.<server-name>tomcp.plugin.<plugin-name>.<server-name>; the old key was shared by unrelated plugins and is no longer read. AConfigScopenow hasdispose()to release its live namespace and watchers while retaining saved overrides. Plugin disable and failed enable release their scopes, and overlapping enable, disable and uninstall calls for one plugin id now settle in call order.Plugin MCP drift is now tracked by full plugin source id rather than bare server name: hosts that relied on a drift event between two unrelated plugins using the same local server label should compare those sources explicitly. The baseline now follows the one admitted listing actually mounted as tools, including reconnect and list-change refreshes; a second startup listing is gone.
- —
b7d83ec:
wait_for_jobnow returnssuccess: truewhen its idle or total wait bound is reached. Previously that outcome returnedsuccess: falseand was treated as a failed tool call. Callers that usedsuccess === falseto detect a wait timeout must checkdata.timedOut(idleorwall) instead. The job keeps running in either case.The tool now includes a bounded partial output preview and absolute
nextOffseton timeout, and acceptsfrom_offseton the next call so callers can continue without receiving the same bytes again. It reports output omitted by the wait's 32 KiB cap separately from bytes lost by the job registry's retention cap.
Minor Changes
- —
2b1cbb5: Hosts can use
QueryParams.onJobNoticeDelivered(jobIds)to distinguish background job exits recorded for the model from exits that still need delivery on a later turn. The CLI now keeps a late exit pending for the next turn, retains it after a failed or aborted send, and shows its transcript row once. - —
b7d83ec: Built-in memory stores now offer opt-in revision-checked updates and deletion. Read an opaque token with
getVersionedRecordorread_memory, then pass it toupdateIfRevision,deleteIfRevision,update_memory, ordelete_memory. A stale token reports a conflict without applying the change. Existing calls without a revision keep their last-writer-wins behavior; custom stores need to implement the full conditional interface to accept revisions.In-memory revisions cover cloneable BigInt, Map, Set, Date, cyclic metadata and the full backing buffers visible through typed-array views. Direct resizable
ArrayBuffervalues include their resize attributes. When metadata cannot be fingerprinted synchronously, including views backed bySharedArrayBufferor resizableArrayBuffer,read_memorystill reads it but offers no revision; conditional writes fail closed.When a tool-result guardrail rewrites text, it also replaces separate model-visible content blocks and the original failure error, so neither channel can bypass redaction.
- —
b7d83ec: Memory searches can now bound candidate scans with
maxScannedand continue usingscanOffset. Results reporttruncated,scannedCountandnextScanOffset, withtotalCountcounting only matches in an incomplete page.search_memoryuses a 256-candidate page and acceptsscan_offset; repeat with the returned offset when an older memory may match. Automatic recall also searches at most 256 recent candidates by default, configurable throughMemoryRecallOptions.maxScanned. Disk stores select the page before reading its bodies. A directlistcall withoutmaxScannedretains its full-search behavior.
Patch Changes
- —b7d83ec: The SDK unit test runner now uses at most four workers on machines with many CPU cores. This prevents valid asynchronous tests from hitting their wall-clock timeout under test-worker contention; package runtime behavior and APIs are unchanged.
- —
Patch Changes
- —
57ae7c7: Background jobs whose process cannot start now emit one exit update after the child closes, without reporting the spawn error as a process exit code. An error on a process that already started no longer marks its job as exited while it is still running. SDK API signatures are unchanged.
Completed and aborted job or delegated-task waits release their polling timers instead of keeping them alive for the rest of a long session. Idle-stream, agent front-door, awaited-job and large-input lexer regression tests now avoid real-time races that could fail on a busy CI runner.
The CLI's background-child and scheduled-run integration tests now wait for the event they assert, leaving their normal process deadline to Vitest instead of letting a short poll or unrelated watchdog decide the outcome.
- —
Minor Changes
- —072bb74: Windows computer use can capture pixels for one named window with
screenshot {window_id}and keep background clicks and drags, text and keys bound to that window. Windowtype_textandkeycan explicitly requestdelivery_mode: 'foreground'for browser text or modifier shortcuts that cannot run in the background; this may briefly bring the verified window forward. The pinned driver setswindowScroll: falseand refuses scoped pixel scrolling because its wheel input cannot prove which window receives it; a host that supports safe window scrolling can offer it. Capture identity and window geometry are checked before input; moved windows, old captures and restarted driver sessions require a new window screenshot. Hosts can offer window capture without display capture, in which casewindow_idis required and plain display screenshots remain unavailable. A failed window capture never broadens to a display capture. When both browser and computer-use tools are ready, the CLI routes a named existing browser window to computer use, leaving browser tools for the separate Namzu-managed profile.
- —072bb74: Windows computer use can capture pixels for one named window with
Minor Changes
- —
25966a5: Scheduled script proposals now explain the permission set they actually need, including why
read-onlyblocks every script and why a home-directory working folder is refused. The CLI shows a compact, scrollable confirmation for model proposals and/scheduleconfirmations, with the exact source available for review. It labels pure-script permissions accurately and explains a blanketbashdenial without suggesting a script rewrite.New
notifyOnFinishon the schedule tool and--notify-finished true|falseon the CLI let a polling job suppress Namzu's generic success notice while keeping failure notices. The default remainstrue; existing jobs keep their behavior until changed.
- —
Minor Changes
- —3a0daac: Allow delegated CLI agents to opt into a separate managed Git worktree with
workspace: "worktree". The child runs from the selected checkout's committed HEAD, preserving the parent's directory beneath the Git root. The worktree remains available for review after any outcome. SDK callers can choose a per-child shared or isolated workspace and explicitly retain it; isolated requests accept a checked relativesubdirectory. A manager can make omitted choices shared withworkspaceDefault: 'shared'; existing SDK backend provisioning and cleanup remain the defaults. - —3a0daac:
MCPClient.callToolacceptsonProgressand requests a unique MCP progress token per tool call. Concurrent calls receive only their own validated, bounded progress updates; callbacks stop on completion or cancellation. Streamable HTTP dispatches progress while the SSE response remains open and releases the reader as soon as the matching final reply arrives. MCP tools forward updates throughToolContext.reportfor live host and CLI status. - —3a0daac: Modern MCP connections now follow advertised tool, prompt, and resource catalogue changes through
subscriptions/listen.mcpToolsetrefreshes its live definitions after an acknowledged change, and Streamable HTTP reads the subscription incrementally instead of waiting for the long-lived response to end. Hosts usingStreamableHttpTransportdirectly can usesendSubscriptionfor a bounded SSE stream; existing legacy notification behavior is unchanged.
Patch Changes
- —c8bb87a: Describe a browser
http-authhandoff as an HTTP authentication challenge. The old "password prompt" wording was inaccurate for responses using challenge schemes such as Bearer. - —3a0daac: Reject an explicitly isolated child workspace when a registered Git worktree driver returns a missing path or a path that resolves to the caller's directory. SDK hosts using a custom driver must return an existing, separate checkout.
- —3a0daac: MCP Streamable HTTP connections now surface a 401 or 403 response to the modern protocol probe as an access failure, without attempting a legacy initialize handshake. Correct the server credentials or access policy and reconnect.
- —3a0daac: Modern MCP subscriptions now stop retrying a server that permanently refuses
subscriptions/listen, including HTTP 404 with JSON-RPC method-not-found. Temporary server errors and rate limits still retry with backoff. Operators get a warning and can reconnect after changing the server configuration. An HTTP 200 JSON-RPC method-not-found refusal also stops retries, while a capacity refusal still retries. The response body is read with size and time bounds and released on completion, timeout or disconnect, preventing held responses from accumulating.
- —3a0daac: Allow delegated CLI agents to opt into a separate managed Git worktree with
Major Changes
- —
567ada8: The SDK now accepts application-defined agent type strings and provides
QueryAgentas the generic managed query adapter.defineAgentgives itsruncallback a required fourthAbortSignalparameter and creates fresh instances for delegated turns. Code that invokes aDefineAgentOptions.runcallback directly must supply that fourth argument; existing three-argument callback implementations remain assignable.ReactiveAgentand the supervisor, pipeline and router exports remain available as deprecated compatibility names. CLI delegated turns use a CLI-ownedNamzuCliAgentwhile keeping their existing configuration behavior. - —
567ada8:
PluginHookDefinitionnow checks each hook's returned action against its event. A TypeScript plugin whose handler is declared to return the fullPluginHookResultunion, or returns an action the runtime cannot use for that event, may stop compiling. Narrow the handler return type toPluginHookResultFor<'event_name'>or let TypeScript infer its specific result. For example,retrybelongs topost_tool_use,modifytopre_tool_use, andannotatetouser_prompt_submit. Session start/end and turn interrupt hooks can only declarecontinueas a result. JavaScript runtime behavior is unchanged. - —
567ada8: Plugin tools now carry a source for their owning plugin (
plugin:<name>) or MCP server (plugin:<name>/mcp:<server>), so source authorization can distinguish them. Plugin MCP servers usemcpToolset, gaining live tool and prompt discovery, policy-filtered deferred resources and held changed definitions. Plugin MCP prompt tool names change from<plugin>__mcp_prompt_<server>_<name>to<plugin>__mcp__<server>__prompt__<name>; update saved tool names and permission rules that refer to them.MCPToolsetOptions.reconnectalso accepts a function that returns the current reconnect policy. - —
567ada8: Every registry now throws on a duplicate id by default instead of silently overwriting or skipping.
ManagedRegistry's own default flips from warn-and-overwrite to throw (RegistryCollisionError), and the same convergence applies to registries that were not built on it:AdvisorRegistry.registernow throwsAdvisorCollisionErrorinstead of overwriting in silence;SkillRegistry.addnow throwsSkillCollisionErrorinstead of overwriting in silence;EnvironmentConnectorManager.registerEnvironmentandTenantConnectorManager.registerTenantnow throwEnvironmentCollisionError/TenantCollisionErrorinstead of logging a warning and silently keeping the original.AgentRegistry,ConnectorRegistryandHostCommandRegistry(built onManagedRegistrywith no registry-specific override) throw by the same default flip.PluginRegistryis the named exception and keeps its overwrite behaviour.ToolRegistryis removed in this release; toolset name collisions throwToolsetConflictError. PassonCollision: 'warn-overwrite'explicitly if you build your ownManagedRegistrysubclass and need the old default. A single call that needs to replace one entry on a registry that now throws calls the newManagedRegistry.replace(id, item)instead — seedocs/sdk/registries.md.Every SDK
*CollisionError(ToolNameCollisionError,HostCommandNameCollisionError,ConfigNamespaceCollisionError,PromptContributionCollisionError,ReadModelCollisionError,InvariantNameCollisionError,ProbeNameCollisionError,DuplicateProviderError, plus the four new ones above, plusDelegateIdCollisionError(scheduler/delegating.ts)) now extends the newRegistryCollisionErrorbase class; names, messages and fields are unchanged, and a catch block naming the concrete class still works. A catch that wants "some registry collided" without naming every class can catchRegistryCollisionError. See docs/sdk/registries.md.If you called
register/registerEnvironment/registerTenant/SkillRegistry.adda second time under the same id expecting a silent overwrite or a silent skip, that call now throws. Callunregisterfirst, or (on a registry built onManagedRegistry) call the newreplace(id, item)instead. - —
567ada8: Removed
ToolCatalog,createToolCatalogFromRegistry,loadingFromAvailability,toolDefinitionToCatalogEntry,ToolsetDefinition,ToolsetPolicy,ToolCatalogEntry,ToolCatalogSearchResult,ToolCatalogSnapshotandToolLoadingMode. This was a parallel, unwired model of sources/toolsets/tools with a per-tool policy: no query run ever calledtoLLMTools/getToolsByLoading/searchToolson it. This release also removesToolRegistryand moves live discovery toToolManagerandToolResult.reveals; see thetool-registry-removedchangeset.ToolSourceandToolSourceKindstay.If you constructed a
ToolCatalogdirectly or calledcreateToolCatalogFromRegistry, passtoolsetsto the runtime and useToolManager'slistNames()/availability(name)for an advanced host's roster view. Nothing else in the kernel ever populated the catalog from live state. - —
fbeac55: A scheduled job can now be a fixed shell script instead of a model prompt, or a cheap "wake-gate" script that decides whether to run the model at all —
namzu schedule add --kind script|script+agent, or the model'sscheduletool withkind/script. Ascriptjob spends zero tokens and opens no session; ascript+agentjob runs a gate script first and calls the model only when it prints{"wake": true, "context": "..."}. The script body is checked against the scheduled-run floor (the whole text, exactly as a livebashcall is read) and then everydenyrule — the operator's own and any config file's — per lexed command;allow/askrules andunmatchedare never consulted for a script, since it is fixed, human-confirmed text, not a call a model improvises. Ascript+agentjob'sallow/ask/unmatchedrules still govern its AGENT phase only, unaffected by the script's own (deny-only) check.@namzu/sdk(major)- —Breaking type correction:
ScheduleJobPreview.modelis optional because a purescriptjob has no model. Hosts that display it must handle its absence forrunKind: 'script'; previews foragentandscript+agentstill carry the pinned model. - —
ScheduleJobPreview.networkAccessnow includes a host script or an allowed host shell that could reach the network, even without web/browser tool grants. Its new optionalnetworkGrantAccessseparates those grants from shell capability; a host implementingpreviewUpdateshould set it so a kind-only conversion can be checked against the network-beside-host-shell rule without refusing every script. An older host that omits it is treated conservatively usingnetworkAccess. - —An unattended sandbox-escape opt-in no longer waives review of a runtime-chosen program name or another call's outside-root path in the same batch.
- —Security fix: a recognised wrapper such as
env,nice,sudo,busyboxortoyboxcan no longer hide a shell's-cpayload from review. Literal payloads are read for permission checks; an expanding payload requires review and is refused in unattended runs.script -t -cnow recognises-cas the command option;-Tconsumes its required timing-file value. - —Security fix:
find's-execfamily now follows wrappers and shell-cpayloads inside each clause, including when another wrapper launchesfind. A dynamically selected program or afindplaceholder in a shell payload requires live review and is refused in an unattended scheduled script. The scheduled-run floor also sees service-changing tools behind wrappers in these clauses. - —Breaking:
ScheduleJobDraft.promptis now optional (prompt?: string), since a purescriptdraft has none. AnyScheduleToolHostimplementation that readsdraft.promptas a barestringneeds a null check (or to keep reading it only whendraft.runKindis'agent'/'script+agent', where it is still guaranteed present). - —
ScheduleJobDraftgainsrunKind?: 'agent' | 'script' | 'script+agent'(absent ='agent', unchanged from before) andscript?: { body, shell, timeoutMs? }.ScheduleJobPreviewgains the samerunKind/scriptfields so a host's confirmation screen can show the exact text. - —The
scheduletool's input schema gainskindandscript;createrequiresscriptunlesskindis'agent'and requirespromptunlesskindis'script', and refuses an agent draft that also carries a script. The existing network-beside-a-host-shell refusal now also covers ascript/script+agentjob on the host: the script IS shell code, not a rule a live call might reach later. - —Security/UX fix:
updateacceptedkind/scriptin its input schema but silently dropped both from the change it actually applied, reporting success with no error or warning that the model's new script was ignored.ScheduleJobChangesgainsrunKind/script(additive, so this alone would beminor) andupdatenow forwards both toScheduleToolHost.previewUpdate, re-verified fresh against the scheduled-run floor and everydenyrule exactly like a new job's, and shown in full — with what changed — on the same confirmation screencreateuses. A host that does not yet read the new fields offScheduleJobChangessilently keeps a job's existing kind/script on every update, the same as before this release; only@namzu/cli's own host (below) newly acts on them. - —Security fix:
updatechecks the effective preview's network grants after applying every change, so a kind-only conversion cannot inherit web/browser access beside a newly introduced host script. A call that changes the kind and leaves permissions unset is checked too. - —New exports from the root entry:
execHostShell,ExecHostShellProgress,CommandShell,CommandShellProbe,findCommandShell,hostCommandShell,hostShellSpawn,withoutBashStartup— the shell-resolution and spawn machinery thebashtool already used internally, now available to a host that runs a command line outside a live tool call. - —
findCommandShellForDialectandinstalledCommandShellForDialectselect an installed bash or sh explicitly, returning no shell if the requested interpreter is absent.execHostShellaccepts thatCommandShellthrough its optionalshelloption, keeping its existing process supervision and output capture for hosts that choose an interpreter themselves. - —Security fix, in the scheduled-run floor, so a live
bashcall benefits too:trap 'ACTION' SIGNALnow readsACTIONas a command line (it runs when the signal fires);find … -exec|-execdir|-ok|-okdir … ;|+is refused when its clause contains{}and the search root is unknown or could reach NAMZU_HOME, a-name/-pathfilter could match NAMZU_HOME's own name, or the clause's own program can stop or remove a service; running a file the SAME command line wrote earlier (>,>>,tee,cp/mv, then./x,sh x,bash x,. x,source x) is refused, since its content was never confirmed. - —
lexShellCommandLinenow reads$(…)and backtick bodies as nested command lines (the same treatmentbash -cpayloads already got) instead of calling the whole containing line opaque outright: each command inside a substitution is inShellLexResult.commands, markedorigin: 'substitution', checked by the scheduled-run floor and by everydenyrule exactly like the rest of the line. Two shapes stay opaque because what actually runs cannot be pinned down from the text alone, measured against real bash (5.2.21 and 5.3.15): a command substitution beside brace expansion in the same word ($(cmd){a,b}runscmdonce per alternative, not once) and an unquoted substitution used as a</>redirection target (a${var:-…}-style default value can run its substitution twice when the target turns out ambiguous) — both are refused the same way an unmodeled construct always was.${ list; }(bash 5.3's brace-form substitution) and process substitution (<(…),>(…)) are unchanged, still opaque; out of this scope.ShellWordgainssubstitutes: boolean(additive). Proven differentially against a real bash on the host (command_not_found_handleargv capture, disabled builtins, emptyPATH): every existing corpus line, the exhaustive up-to-three-token sweep, a 6,000-line seeded random sample (manually run to 150,000 with zero mismatches before landing), and new hand-written cases for nesting, mixed quoting, and the two constructs above.decomposeCommandLine'ssegments/opaquechange the same way for these two constructs. - —Security fix, follow-up to the above: a second review found that the scheduled-run floor's own
pkill/killall/systemctl/launchctl/schtasks/D-Bus detection read a wild (expanding) program-name word as NOT being the tool in question unless its raw, unevaluated text happened to contain the tool's name as one contiguous run —$(echo pk)ill -f nodeand$(echo system)ctl stop $(echo namzu)-scheduler.serviceboth passed. A wild word in the program-name position (the head, or right after asudo/env-style one-level prefix) now counts as being every tool this checks for; a wild word elsewhere (an ordinary argument, a path) is unaffected.find ~/.namzu -exec rm {} \;-style commands are unaffected: the fix is scoped to the program-name position, not to "any wild word anywhere." - —Security fix, live tool authorization behaviour changes (
packages/sdk/src/runtime/query/): the same review found that adenyrule written against a command's real name ("git push*": deny) never matches one produced by a substitution or a variable ($(echo git) push,`echo rm` -rf x,$X push, or a variable in the program's path,"$HOME"/bin/tool), since the name never appears as such anywhere in the call's text — for the scheduled-run floor's script check (verifyScheduledScript, which now refuses such a command outright, naming it, the same posture as an opaque script) and, pre-existing and more broadly, for the livebashtool's own permission rules. An operator now sees more review prompts: a bash call whose lexed command's own program-name word expands is escalated (ToolCallSummary.escalation.unknownProgram, new field) the same unconditional way a sandbox escape already is — never approved by an allow rule, a remembered grant,accept-editsorauto/an unattended turn, asked about in every mode that gets that far (naming why: "the program this runs is decided at runtime:<text>"), and refused outright with no prompt (UNKNOWN_PROGRAM_UNATTENDED_REFUSAL, new export) — there is no unattended opt-in for this the wayunattendedSandboxEscape: 'allow'is for a sandbox escape. Adenyrule that matches some other way (the tool's own name, an unrelated word) still refuses the call outright, unaffected. Argument-level expansion alone does not trigger this — only the program-name word — so an ordinaryecho $(date)is unaffected. New audit actionsunknown_program(approved/refused). Seedocs/sdk/escalations.md. - —Security fix, follow-up to both of the above — one extra word defeated every one of them: a third review found that all three consumers (the escalation, the scheduled-run floor,
verifyScheduledScript) only ever looked at a command's literal head word, soenv $(echo git) push,command $(echo git) push,exec $(echo git) push, and a re-exec wrapper with a mandatory argument of its own before the program (timeout 5 …,stdbuf -oL …,chrt 0 …,env VAR=value …) all ran, or passed the script check, unverified — the floor's one-hopREEXEC_PREFIXguess put the program at the wrong word entirely for these, andexec/command/taskset/time/builtin/xargswere not in it at all.eval/source/.were not read as unverifiable either, thoughcommand-line.tsalready treats them as opaque fordeny/allowrules. New SDK module,packages/sdk/src/authorization/program.ts, exported from@namzu/sdk:programPositions(command, dialect)is the one place "where does this command actually exec a program" now lives — it unwraps a chain of re-exec wrappers (sudo,doas,pkexec,env,nice,ionice,nohup,setsid,timeout,stdbuf,chrt,taskset,time,command,builtin,exec,xargs) with each one's own real option grammar, failing closed on an option it does not recognise, and reads eachfind -exec-family clause as its own position.source path/. pathandeval word…are read the same way a nestedbash -c '<literal>'payload already is: a LITERAL path is known, exactly asbash pathis (nothing inspects either one's contents), and a LITERALevalpayload is joined and lexed as a command line of its own, with every position inside it folded in (soeval 'env $(echo git) push'is still unknown, transitively); an expanding path/argument word, or a payload that does not read cleanly, is unknown.hasPoisoningPrefix/poisonsLaterCommands/poisonedProgramPosition/resolveScriptPrograms/DYNAMIC_RESOLUTION_VARIABLEScover the other half: a command-prefix orexport/bare assignment toPATH,LD_PRELOAD,LD_LIBRARY_PATH,BASH_ENV,ENVorIFSmakes a LATER literal program name unverifiable too. Behaviour change:unknownProgram's text is now the command plus why its program's position could not be resolved, not a bare word — a host that pattern-matches the exact string should match on substring/prefix, not equality.ToolCallEscalation.unknownProgram's doc comment,escalationReason's wording, and the floor's ownheads/REEXEC_PREFIXmechanism (removed, replaced byprogramPositions) all changed accordingly.packages/sdk/src/authorization/__tests__/program.test.ts(new). Seedocs/sdk/escalations.md,docs/sdk/command-lines.md,docs/cli/scheduled-tasks.md. - —Security fix, two CRITICAL bugs from a final review of
program.ts, plus wrapper coverage: (1)sudo's own case never skipped aNAME=valuepair the wayenv's already did, sosudo VAR=value $(echo ls)reported the ASSIGNMENT WORD as the program and never examined the real, wild one after it; bothsudo's andenv's assignment-skipping now also feed a poisoning name (PATH=,LD_PRELOAD=, …) into the poisoning check instead of silently passing it through. (2)executor.ts'sunknownProgramOfignored the reading's ownopaque/!completeflag, sobash -c "$X"— whose payload the lexer cannot follow at all — was never escalated; a new exported function,unknownProgramInLine(value, dialect), checks that first, andexecutor.tsnow delegates to it rather than re-implementing the walk. New wrapper coverage:busybox/toybox(transparent applet dispatch);declare -x/typeset -x(any option cluster with it, never+x) now poison likeexport, andexport -ncorrectly does not;su -c/runuser -c/script -c/flock -cread a literal payload recursively like a nestedbash -c '<literal>'(su/runuserwith no-care unknown: an interactive login shell);flock's positional form;unshare,nsenter,chroot(unknown with no command),setpriv,prlimit,numactl;watch(default payload joined and lexed likeeval's,-xexecs the argv directly);sudo -i/-s/-e/--login/--shell/--editand baresudoeditare explicit unknowns.ssh host cmdis deliberately not modelled (the command is remote);docs/sdk/escalations.mdnow documents this residual boundary — the full modelled-wrapper list, and that an unmodelled one is treated as the program itself, withdenyrules, the floor and ordinary review still applying to it.packages/sdk/src/authorization/program.ts,runtime/query/executor.ts,packages/sdk/src/authorization/__tests__/program.test.ts.
@namzu/cli(major)- —A pure
scriptjob now creates and runs without a configured provider or--model. New script jobs omit the unusedmodelfield; older confirmed script jobs that already store a real model still read with their confirmation digest intact and keep it on a script edit. Explicit--model,--effort, prompt, agent budget, provider wait, approval TTL, session retention and browser grant inputs are refused for a pure script instead of silently ignored; older stored fields remain readable but inert.agentandscript+agentstill require a model; converting a model-free script to either kind requires one. Text previews, the SDKScheduleJobPreview.budget, andschedule showreport zero model tokens and the script timeout for pure scripts. The schedule tool's success message reports pure script history without claiming a session. - —Breaking, on disk: the job/run-result/history file format moves to
v: 2for a job that actually usesrunKind/script, acheck-failedstatus, orgateResult/scriptOutput. An older namzu that reads such a file refuses it loudly ("was written by a newer namzu...") rather than misreading a script job as a malformed agent job. A plainagentjob staysv: 1, byte-for-byte as before, and every existing job keeps reading and confirming exactly as it did — downgrade only affects a job you have actually converted toscript/script+agent. - —Fix: a job file an older namzu (or a hand edit) left in a format the current one refuses to fully parse was previously invisible to
createJob's name-uniqueness check and tofindJob(by name or id prefix) — a name collision with it went undetected, and looking it up said "No scheduled job is named…" instead of the real reason. Both now read a refused file'sid/nameleniently, off the raw JSON, without validating anything else about it: a name collision with such a file is now refused, naming the file and the reason; looking it up by its own name or a matching id prefix now reports the real reason (e.g. "written by a newer namzu…"); a name or id prefix matching more than one job — readable, unreadable, or a mix — is an error listing every match and how to address each.schedule listsaysN job files could not be readinstead ofNo scheduled jobswhen every job file is such a file. - —New
schedule add/editflags:--kind agent|script|script+agent,--script/--script-file,--shell bash|sh,--script-timeout. Ascriptjob needs--shelland a non-empty script and has no--prompt;--unmatched parkis refused for a purescriptjob (nothing can wait for the operator mid-script);--execution sandboxis refused forscript/script+agentin this release;script/script+agentjob creation is refused outright on native (non-WSL) Windows, where the only available shell reading is a loosecmd.exeapproximation the floor cannot fully verify. - —Fix:
--shell shpreviously only labeled a script for verification while fire always used the live bash tool's automatically chosen shell. On a bash host, a newly confirmedshjob was guaranteed to stop at first fire. A script now executes under its chosen installedbashorsh; creation and every confirmation refuse an absent interpreter, and fire blocks if it has since disappeared. Native Windows remains refused. - —
ScheduleRunStatusgainscheck-failed(a script or wake-gate malfunctioned: non-zero exit, its own timeout, or stdout that is not the wake-gate's JSON contract) — CLI-internal, not part of the SDK's public surface. It joins the failure bucket for the failure streak and automatic pause, and gets its own notification andnoticeTextcase. - —
schedule show/history --jsongaingateResult({ wake, contextChars }) andscriptOutput({ stdout, stderr }, capped with an explicit truncation marker) on a firedscript/script+agentrun;show's text view gains a "Script"/"Wake-gate script" section beside "Prompt". - —
schedule list(text and--json) now shows each job's kind:[script, 0 tokens]and[script+agent]in the text view,kind(absent foragent) in--json. The TUI's/schedulelist marks the same two kinds. Found missing in a UX review — nothing distinguished a zero-tokenscriptjob from anagentjob in either listing. - —Security fix: a
script/script+agentrun's own output — a wake-gate parse failure's quoted stdout, a script-check refusal's quoted command, acheck-failednotification's reason,scriptSummaryOf's extracted summary line, andschedule show/history's text-viewreason/summary— is untrusted runtime text, not anything an operator confirmed, and is now run through the samesanitizeLine()a model's own answer already is before it reaches a terminal or a desktop notification, so it cannot plant control characters, terminal escape sequences or invisible/bidi characters on the operator's screen.--jsonoutput is unaffected and stays raw. - —Security fix: CLI and TUI schedule add/edit/confirm/list/show text views now show terminal controls and bidi characters in script text, prompts, changed lines and folder paths as visible code point escapes, so a script cannot erase or reorder its own confirmation. The model's schedule-tool confirmation uses the same projection. Persisted jobs and JSON output preserve the original source text.
- —Security/UX fix: the TUI's
scheduletool host now acts on anupdate'srunKind/script: previously it always carried a job's existing kind and script forward unchanged, silently, whatever the model asked to change. Moving tokind: 'agent'drops the script (an agent job cannot carry one); a change that toucheskindorscriptshows up in "Changed since it was last confirmed" the same as every other field, alongside the new script's full text in its own section (packages/cli/src/tui/schedule/tool-host.tsupdateRequest,packages/cli/src/schedule/changes.tsconfirmationView). - —Security fix:
namzu schedule __firenow refuses ascript/script+agentrun outright on native (non-WSL) Windows, the same way job creation already does. Job creation alone was not enough: a job confirmed on WSL or Linux, whose daemon later finds itself running on native Windows (a movedNAMZU_HOME, a machine re-imaged from WSL to a native install), previously ran anyway whenever the job's confirmed shell dialect happened to still read assh— the label native Windows'scmd.exeapproximation and POSIX sh both report, so the existing dialect-mismatch check alone could not tell them apart. - —A
script/script+agentscript (and the scheduled-run floor generally, so a livebashcall benefits too) may now use$(…)/backtick command substitution: it is checked the same as the rest of the script instead of refusing the whole thing outright (see the SDK'slexShellCommandLineentry above). Theschedule-taskskill's guidance on this is updated to match; the two narrow shapes still refused (a substitution beside brace expansion, or unquoted as a</>redirection target) are named there with why.
Upgrade step: nothing changes for an existing
agentjob. Adopting ascript/script+agentjob on aNAMZU_HOMEyou may downgrade the CLI on later means that job's files become unreadable by the older version until you upgrade again; keep that in mind before converting a job you might need to roll back. - —Breaking type correction:
- —
567ada8: Continuing a session with a different project, tenant or topic now fails with
invalid_configbefore its history, budget or queued topic messages are used. A supplied session log must also name the requested session. Pass the complete identity returned by the firstrunAgentcall on later turns, or use the same recorded scope withqueryandQueryAgent.resumeSession,loadTurnState,loadSelectedTurnStateandprepareForkStateverify the source log before reading checkpoints.PrepareForkInput.scope.topicIdis now required: changescope: checkpointScopetoscope: { ...checkpointScope, topicId: sourceTopicId }, using the source session's recorded topic.Older session logs whose
session_startedrecord lackstenantIdortopicIdremain readable but can no longer be continued, resumed or forked because their owner cannot be established. To migrate, verify the owner outside the log, start a new session under that scope and seed it with trusted conversation messages. Keep the old hash-chained log unchanged.Custom
SessionLog.claimimplementations must honor the newrepairTornTail: falseoption used during owner admission. Defer torn-tail truncation and repair records until a later authorized append when that option is false. The session-log conformance contract is now version 2; update a custom backend's declaredcontractVersionafter it passes the new deferred-repair case. - —
567ada8:
ToolMessage.revealedToolsnow stores{ name, sourceId, sourceKind }receipts instead of name strings. Code that constructs these messages directly must include the source of each revealed tool; old name-only receipts in stored sessions no longer load deferred tools and must be rediscovered. A failed tool call no longer reveals tools. Hosts whose tools need a live connection should wrap their toolset withreadyWhen(toolset, () => connectionIsReady);deferred(...)only delays schema loading andsearch_toolscan load it whenever the host reports ready. A caller-providedsearch_toolsmust be active and ready when deferred tools are present, and a runtime override cannot defer or suspend it. - —
567ada8:
ToolRegistryis removed. The runtime now resolves tools fromToolsets (plan.md v3 §2) through aToolManager(toolsets/manager.ts) thatquery()/drainQuerybuilds for itself, once per turn, from thetoolsetsyou pass — it is never mutated by the runtime, and its own generated tools (task tools,search_tools, the structured-output tool, advisory tools) are combined in as aruntimetoolset rather than injected into your input.What breaks, and what to do about it:
| Removed / changed | Replace with | | ---------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | |
tools: ToolRegistryContractonQueryParams,ReactiveAgentConfig,RunAgentOptions,BidiTurnParams|toolsets: readonly Toolset[]. Wrap a plain list withtoolset('name', [...]);deferred(toolset(...))for a toolset whose tools start deferred. | |tools?: ToolRegistryContractonSupervisorAgentConfig|toolsets?: readonly Toolset[](same shape). | |ToolRegistryclass,ToolRegistryContract,ToolRegistryRef,ToolRegistryForkOptions|ToolManager(new ToolManager({ toolsets, messages: () => turnMessages, resultGuardrails?, tierConfig? })), exported as an advanced API.ToolContext.toolRegistryis now aToolsView(has/availability/searchDeferred— noactivate, nosearchActive). | |registry.fork({ deferExcept })| No replacement: build thetoolsetsarray you want for that turn/send instead of forking a shared registry. There is no live shared registry to fork from any more. | |registry.activate(names)/.defer(names)/.suspendAll()/.hasSuspended()/.assignTiers()| Gone..defer/.suspendAll/.hasSuspended/.assignTiershad no production caller;.activate()did (runtime/query/executor.ts's reveal handling,tools/builtins/search-tools.ts'ssearch_tools), and both are now the same derived mechanism. Availability is now DERIVED, never mutated: a tool is'active'unless its toolset declared'deferred'and no tool message in the turn's post-compaction history has revealed it (ToolResult.reveals, persisted asToolMessage.revealedTools). A tool's own result reveals names for the rest of the turn; nothing callsactivateany more, includingsearch_tools. | |registry.searchActive(query)| Gone.ToolManager.searchDeferred(query, limit?)remains, with an optional result cap. | |ToolDefinition.provenance/ToolProvenanceon a tool |ToolManager.sourceOf(name)— a leanToolSourceRef(id,kind, and formcp_server,server+readOnlyHintTrusted) that names the OWNING TOOLSET, not the tool. A definition can no longer claim its own source.tools/trusted-read-only.ts'sisTrustedReadOnlynow takes that source as a third argument.ToolProvenanceitself stays, as the shapescreenToolResultreads. | |filterReadOnlyTools(registry)/filterToolsNamed(registry, names)(tools/roster.ts) |filtered(toolset, (tool, source) => isTrustedReadOnly(tool, undefined, source))/filtered(toolset, names)(toolsets/wrappers.ts) — keeps the inner toolset's ownavailabilityand stays live over a live source, instead of freezing an always-'active'snapshot.sourceis the ONE toolsetfilteredruns on: filter each of a wider roster's contributing toolsets this way before combining them, never a toolsetcombineToolsetsalready merged from several sources (tools/roster.tssays why). | |ConnectorToolRouterclass (registerTools/unregisterTools/refreshToolsmutating a registry) |connectorTools(manager, { strategy? })(connector/tools/router.js) — a plain function returningToolDefinition[]; wrap it intoolset(...)yourself. Never had a production caller. | |mcp_<server>_<tool>naming from the CLI's own MCP path |mcp__<server>__<tool>; update saved prompts, permissions and integrations that name these tools. The CLI now mountsmcpToolsetentries for each server.mcpToolToToolDefinitionno longer sets.provenance; source identity and read-only trust belong to the owning toolset. | |PluginLifecycleManagerConfig.toolRegistry| Removed.PluginLifecycleManagerowns its tool contributions and exposes.toolsets: readonly Toolset[]for a host to fold into its owntoolsetsarray. Each plugin has its own live file-tool source and each plugin MCP server has its own source; see theplugin-source-toolsetschangeset. | |PluginResolver's second constructor argument | WasToolRegistryContract; nowPick<ToolManager, 'listNames' | 'has'>. | |ToolRegistryConfig,ToolCatalogand companions |ToolManagerConfig(toolsets/manager.ts) is the manager's construction config. See theremove-tool-catalogchangeset for the catalog removal. |The CLI, AG-UI adapter and other affected workspace packages migrate in this same release; see their package changesets.
@namzu/filesand@namzu/lspdo not use the removed registry API.A caller toolset that contributes a name
query()also generates internally (a task-tool name,search_tools, the structured-output tool's name, or an enabled advisory tool's name) is refused at construction withToolsetConflictError, naming both sources — this generalisesSupervisorAgent's old hand-written refusal of a caller tool shadowing one of its six coordinator names, which is now the same mechanism (its coordinator tools are just another toolset).search_toolsno longer activates anything: it returnsrevealson its own result, like any other tool now can. Its receipt for "no deferred match" no longer echoes already-active matching tools, since the tool-body-facingToolsViewhas no active-tool search — onlysearchDeferred.Security-relevant, additive:
ToolCallContext(authorization/gate.ts) gains an optionaltoolSource?: ToolSourceRef, andAuthorizationGate'sallow_read_onlyrule now reads it the same wayisTrustedReadOnlyalways has. WithoutToolDefinition.provenancetravelling with the tool object, the live gate had no way to tell an operator-trusted MCP server's ownreadOnlyHint: truefrom an untrusted one's — both call sites inruntime/query/iteration/phases/tool-review.tsand the two inruntime/query/executor.ts(nested tool calls) now passToolManager.sourceOf(name). A host that builds its ownAuthorizationGatecalls and does not threadtoolSourcethrough gets the same "no untrusted party known" default as before (host-defined tools were never affected), but should pass it wherever it has aToolManagerto ask. Separately,ToolPredicate(toolsets/types.ts) widens to(tool, source: ToolSourceRef) => boolean;filtered/requireApprovalnow supply the toolset's own source as the second argument, existing single-arg predicates are unaffected. See thefilterReadOnlyToolsrow above for the one-source-at-a-time rule this predicate needs to stay correct across a combined, multi-source roster.
Minor Changes
- —
567ada8: Releases live toolset change listeners when a turn or duplex session ends, including abandoned streams and failed duplex connections, so repeated runs using one MCP toolset do not retain earlier tool managers.
ToolManager.dispose()unsubscribes without closing caller-owned toolsets; directToolManagerusers should call it when finished. Agent front doors now check every exposed config field against its runtime destination, and the deprecatedrunAgent.verificationGateoption again applies the authorization policy. Supplying distinct policies under both gate names fails before the model runs. - —
567ada8: Managed hosts can now pass tenant, project, topic and session attribution per invocation through
ManagedAgentInput.managedScopewhen callingQueryAgent.AgentManagersupplies the admitted child scope itself, andQueryAgentaccepts that scope while continuing to accept the four flat config fields. If both are supplied with different IDs, the run fails before a model call. The generalAgentInputand other agent implementations keep their existing contracts. IdempotentQueryAgentcalls are now deduplicated only within the same managed scope. - —
567ada8: Authorization rules can now match the owning toolset's source id with
{ type: 'by_source', sources: ['mcp:github'], decision: 'review' }. This lets a host ask, allow or deny every tool from one or more MCP servers without listing names individually. A rule with no matching host-supplied source leaves the call to later rules.The CLI accepts
[permissions.sources]with source ids or globs mapped toallow,askordeny, and its permissions display shows those rules and their decisions. - —
567ada8: Added
findUndescribedProperties(schema), exported alongsidefindPortableSchemaViolationsfrom the package root. It walks a rendered tool schema and returns every named object property, at any depth, whose schema carries no non-emptydescription— the same non-throwing, test-time sweep shape as the portability check, for a different defect: a Zod field with no.describe()reaches the model with no account of what it is for. Purely additive; nothing existing changes behavior. - —
567ada8: Add
defineCapabilityanddynamicCapabilityfor reusable host-authored behavior passed torunAgent({ capabilities }). A capability can contribute toolsets, instructions, prompt contributions, input and output guardrails, and per-run model settings. ExistingrunAgentcalls need no migration. - —
567ada8: Two additive changes to the MCP connector, both opt-in and default-preserving:
- —
MCPInitializeResult.instructionsandMCPClientState.serverInstructionscapture the server'sinitializeinstructions string (legacy handshake only — the modern era has noinitializeround trip and never populates this field). This is observability only: nothing folds it into an agent's instruction set automatically, matching namzu's existing rule that server-authored text never reaches instruction/system position unframed. A host that wants to display or log a server's instructions can readclient.getState().serverInstructions. - —
mcpToolToToolDefinitiontakes a new optional 5th parameter,maxRetries?: number. Left unset, behaviour is unchanged: nomaxRetrieson the returnedToolDefinition. When set, the adapter also now marksToolResult.retryable: trueon the two failures it already classifies as not having reached the server's side effect (mcp_tool_input_required,mcp_tool_missing_client_capability) — the field the executor's retry gate actually reads. An HTTP-redirect outcome-unknown failure and any raw transport error stay non-retryable, since whether either one reached the server is exactly what is not known.
Nothing to change for an existing caller: both are new optional fields/parameters with no effect until set.
- —
- —
567ada8:
MCPToolDiscoveryOptionsandMCPToolsetOptionsgainonRefused. The callback reports the current policy refusals for each tool, prompt and resource listing, including an empty list when a later listing clears them. Hosts can show which server names were excluded and why. - —
567ada8: New
mcpToolset(client, options)(packages/sdk/src/connector/mcp/): the path from a connectedMCPClientto two live toolsets mounted together — tools and prompts under the configured availability, plus resources as two always-deferredtools (mcp__<server>__list_resources,mcp__<server>__read_resource, admitting only aurithe server has actually listed).options.allow/options.denygovern all three surfaces — tools, prompts and resources — matched on the server's own name for each. Every name ismcp__<server>__<rest>(tools),mcp__<server>__prompt__<rest>(prompts), shortened deterministically rather than refused when it would exceed the wire's 64-character limit. Reacts tolist_changednotifications (only for a capability the server actually advertised) and to reconnection by re-discovering and reporting throughonChange— including whether the server supports resources at all, re-checked on every reconnect rather than fixed at construction. Owns and stops anMCPReconnectSupervisor(reconnect: { enabled: false }opts out), and releases itsonNotificationsubscription onclose(). Discovery goes through the existingMCPToolDiscoveryallow/deny policy and rug-pull drift detection, now including adiscoverResourcesFromalongside the existingdiscoverFrom/discoverPromptsFrom. Also new:mcpToolsetName(serverName, ...segments)and theMCPToolsetOptionstype. See docs/sdk/mcp-toolset.md.MCPClient.onNotificationnow returns an unsubscribe function, mirroringonLifecycle— additive; any existing caller ignoring the return value is unaffected.Purely additive: no existing export changed or was removed.
mcpToolToToolDefinition/mcpPromptToToolDefinition(the piecesmcpToolsetwraps) remain exported for direct SDK callers. The CLI and plugin lifecycle now usemcpToolset, with their naming changes covered by separate changesets. A caller building an MCP server's tools by hand can move tomcpToolsetat its own pace. - —
567ada8: Plugin manifests can declare
instructions. An enabled plugin contributes the text as labelled, untrusted request context; disabling it revokes the contribution, including for an already-running turn. SDK hosts can also calldefinePlugin({ name, tools, hooks, instructions, mcpServers })andPluginLifecycleManager.installDefined(plugin)to install a host-authored plugin without a manifest file or dynamic module import. - —
567ada8:
PromptContributionRegistry.unregister(id)removes a prompt contribution and returns whether it existed. Hosts can revoke instructions when a plugin or other contribution owner is disabled, then register the id again when it is re-enabled. - —
567ada8:
runAgentaccepts a caller-ownedpluginManagerand mounts its toolsets, enabled plugin instructions, and hooks for the invocation. Hosts using the high-level agent entry point can now pass a configuredPluginLifecycleManagerwithout manually reassembling its contributions. The host still owns plugin admission, enablement, and cleanup. - —
567ada8: Added
ToolManager(packages/sdk/src/toolsets/manager.ts), the runtime-owned resolver ofToolsets that plan.md v3 §2 describes:new ToolManager({ toolsets, resultGuardrails?, tierConfig?, messages })resolves toolsets in order, then tools in order, throwingToolsetConflictErroron a name conflict at construction.refresh()adopts a live toolset's change only when called, reportingadded/removed/drifted/refusednames.availability(name)is derived —'active'or'deferred', never stored — from the owning toolset's own declaration and whether a tool message in the turn's post-compaction history has revealed the name (ToolMessage.revealedTools, also added, the persisted form ofToolResult.reveals).sourceOf(name),toLLMTools/toPromptSection/toTierGuidance/searchDeferred(query, limit?), a narrowview()forToolContext, and theprepareExecution/executePrepared/executepipeline (copied fromToolRegistry's, reading availability and source from the derivation instead of a stored map andToolDefinition.provenance) round it out.ToolManageris exported as an advanced API.query()and the other runtime entry points use it in this release; see thetool-registry-removedchangeset for the breaking input and export migration. - —
567ada8: A tool can now declare that it always needs a person's approval, and carry free-form metadata a host can filter on. Both are additive; nothing existing changes behaviour.
- —
ToolDefinition.requiresApproval?(input): booleananddefineTool({ requiresApproval })(a boolean or a function of the input) mark a call as needing a person's approval no matter what the host's authorization rules say or what mode the turn is running in. It is stronger thanisDestructive: a gateallowrule andauto/unattended mode both still let a destructive call through, but neither can let arequiresApprovalcall through — it survives a gateallowrule the way an escalated call does, is asked about in every mode that reaches it, refused when nobody can be asked, refused outright understrict, and covered by no grant, skill grant oraccept-editsexemption. Adenyrule still wins. Reach for this on a call that is sensitive for a reason other than being destructive (it costs money, it leaves its own audit trail, it is policy-sensitive) instead of misdeclaringisDestructiveto get a review the host might not otherwise configure. See The review policy. - —An unattended sandbox-escape opt-in confirms only the escape. It no longer waives a tool's
requiresApprovaldeclaration or another call's outside-root review in the same batch. - —
ToolDefinition.metadata?: Readonly<Record<string, unknown>>anddefineTool({ metadata })carry free-form, tool-author-declared data for filtering and behaviour customization. Never sent to the model and never read by the runtime itself. The newmatchesToolSelector(selector, tool)matches a tool against a list of names, a partial deep-equal match againstmetadata, or a predicate — for a capability, toolset wrapper or host to target a group of tools without maintaining a parallel name list. - —
mcpToolToToolDefinitionnow carries a connected server'stitle,idempotentHint,openWorldHintand any tool-listing_metainto the newmetadatafield instead of dropping them (readOnlyHint/destructiveHintkeep their existing typed home onisReadOnly/isDestructive). It never populatesrequiresApproval: a connected server cannot demand, or waive, its own review requirement.MCPToolDefinitiongained a matching_meta?: Record<string, unknown>field for the raw listing value.
Nothing to do to keep existing behaviour: every new field is optional and absent unless a tool author sets it.
- —
- —
567ada8: Added
ToolResult.reveals?: readonly string[]. A tool's own result can name further deferred tools to make callable — the same mechanismsearch_toolsuses. The runtime persists admitted names on the tool message, so discovery survives later sends and resume until compaction. Unknown, already-active and out-of-allowlist names are ignored. There is no suspended state or mutable registry activation. Seedocs/sdk/tool-discovery.mdfor the migration fromToolRegistry.activate(). - —
567ada8: New
packages/sdk/src/toolsets/module:Toolset(the unit every tool comes from),toolset(source, tools), the composable wrappersprefixed,renamed,filtered,deferred,requireApproval,withMetadataandmapTools, andcombineToolsets(source, toolsets)with atomic name-conflict detection (ToolsetConflictError, extending the sharedRegistryCollisionError— see docs/sdk/registries.md — withcombineToolsetsas its registry name and the tool name as the colliding id;name, message andfirstSource/secondSourceare unchanged). AlsomatchesSourceIdGlobandtoToolSourceRef. See docs/sdk/toolsets.md.filtered's{ metadata }selector andrequireApprovalcompose with theToolDefinition.metadataandrequiresApprovalfields (see thetool-requires-approval-and-metadatachangeset). Existing callers must migrate fromToolRegistrytoToolsetas described in thetool-registry-removedchangeset.
Patch Changes
- —
567ada8: The AG-UI adapter now reads tools and review metadata from the query's
toolsets, preserving review exemptions and per-tool timeouts after theToolRegistryremoval. Hosts must returnQueryParams.toolsetsfromcreateQueryinstead of passing aToolRegistryastools. Wrap definitions withtoolset(source, definitions)and require@namzu/sdk >=48.0.0; use@namzu/ag-ui2.x with SDK 45.1–47. MCP resource tools likewise rely on their owning toolset for source trust instead of a removed per-tool provenance field. - —
567ada8: CLI MCP tool names change from
mcp_<server>_<tool>tomcp__<server>__<tool>. Update any permissions, saved prompts or integrations that refer to those names; the CLI warns once for each configured permission rule still using the old form. MCP tools now update after server notifications and reconnects. Configureallow,deny,maxRetries,requireApproval,readOnlyHintTrustedandinstructionsper server undermcpServers;instructions: trueincludes the server's text as labelled untrusted request context./mcp toolsshows the server's current tool names and instructions.The SDK now skips prompt discovery when a server did not advertise prompt support and keeps approval-wrapped tool definitions stable across unchanged toolset snapshots.
- —
567ada8: Fixed:
combineToolsetsdropped every input toolset's ownavailability('active'/'deferred') — the mergedToolsetit returned had noavailabilityfield, whichToolManagerand everything else that reads it defaults to'active', regardless of whether some of the merged inputs were wrapped withdeferred(...).query()already avoided this by keeping its own eager and deferred halves as two separate array entries instead of combining them (see its own comment inruntime/query/index.ts), butcombineToolsetsitself stayed exported with this defect for any other caller.combineToolsetsnow refuses to merge toolsets that disagree on availability, naming both sources, instead of silently reporting the deferred side's tools as active. Toolsets that agree — all active, or all deferred — combine as before, and the combined toolset now carriesavailability: 'deferred'when every input does. No current caller passes a mixed set (this was latent, not user-visible); a caller with a genuine eager/deferred split keeps them as two separate entries in its owntoolsetsarray, the wayquery()does. - —
567ada8:
combineToolsetsnow releases earlier live-toolset listeners if a later source fails to subscribe. Its unsubscribe also attempts every inner cleanup when one throws, so failed setup and teardown do not leave other sources observing a finished manager. - —
567ada8: MCP tool definitions that change after initial admission keep serving their earlier definition across turns; new and removed names still update.
/mcpandexec --jsonnow report server discovery changes and current policy refusals, including changed definitions held for review. - —
567ada8: Former MCP permission-name warnings now use the CLI's structured logger instead of writing plain text to stderr. Consumers of
exec --jsonstderr can continue parsing log records when a legacy permission rule is configured. Plugin MCP disconnect warnings use a fixed message and put the operation in a log attribute. - —
567ada8: Shipped prose no longer names other named products.
matchesToolSelector's doc comment (packages/sdk/src/tools/roster.ts) and Tool metadata and selectors describe its synchronous-only design against "some other agent frameworks' equivalent selector" instead of one by name; Tool servers describes the wider${VAR}/${VAR:-default}interpolation some MCP clients accept as "several desktop and editor MCP clients" instead of naming two. Wording only: no type, default or behaviour changed. - —
567ada8: Invalid JavaScript plugin tools now fail when a plugin is enabled, with the plugin and module named, if they omit a usable
inputSchema. Before, a deferred plugin tool could load successfully and then crash the turn with a Zod_deferror aftersearch_toolsrevealed it. Supply a ZodinputSchema, or a compatible parser and explicitmodelInputSchema. - —
567ada8:
@namzu/cli: the session's tool roster is now composed from namedToolsets (@namzu/sdk's toolsets, plan.md v3 §8) instead of aToolRegistry. This is internal composition, not a config or CLI-flag change — nothing innamzu.config.jsonor the command line changes — but it is a breaking change for anything that imported@namzu/cli's internals directly:Removed / changed Replace with createCliPluginRuntime(config, tools: ToolRegistry, cwd, hooks?, skillTool?)createCliPluginRuntime(config, cwd, hooks?)— it no longer takes or mutates a tool registry. Readruntime.skills.sizeand foldruntime.manager.toolsetsinto your own toolsets list; theskilltool is your own toolset now, not something this runtime registers.SubagentRuntimeOptions.buildTools: () => ToolRegistryContractbuildTools: () => readonly Toolset[].SubagentRuntimeOptions.configureWebSearch?: (provider, model, tools: ToolRegistryContract) => WebSearchConfig | undefinedconfigureWebSearch?: (provider, model, toolsets: readonly Toolset[]) => WebSearchConfig | undefined— a pure decision now; it no longer removesweb_searchitself, so a caller building the child's final toolsets filters it out when this returns truthy (filtered(ts, (t) => t.name !== 'web_search')).McpConnection.tools: readonly ToolDefinition[](still present)McpConnection.toolsets: readonly Toolset[], two per connected server (main tools and deferred resources, both kindmcp_server, idmcp:<server>) — prefer these;.toolsis now just the flattened union across all of them.Additive:
AgentSessiongained apresenter: ToolPresenterfield — the session's real tool-call/result presenter, for a host that renders events outsidesend()'s own stream. The ACP bridge (namzu acp) now uses it instead of a presenter built over a permanently empty registry, so tool-specificpresentCall/presentResultviews (a richer diff, a custom label) render over ACP for the first time; every ACP tool view had silently fallen back to the generic label before.Behaviour: because tool availability is now derived from the turn's own revealed-tool history (
@namzu/sdk'sToolManager) rather than a per-send registry fork, a toolsearch_toolsreveals in one send now stays active in a later send of the same session, and afternamzu resume— it no longer resets every send.@namzu/sdk: twocombineToolsetsusage bugs, found while wiring the CLI onto toolsets, are fixed —query()'s own generated tools andSupervisorAgent's coordinator tools are now two separate array entries (an eager one and a deferred one) rather than onecombineToolsets'd together, soruntimeToolOverrides: { name: 'deferred' }(task tools, coordinator tools) is honoured again instead of silently falling back to'active'for both halves.query()also no longer adds its ownsearch_toolswhen a caller'stoolsetsalready contribute a tool under that exact name (a connector genuinely namedsearch_tools), matching the pre-toolsets registry's behaviour and avoiding a spuriousToolsetConflictError. Pure bug fixes, no API change.
- —
Major Changes
- —
82769f1:
computer_usenow shows the model a screenshot sized for it and reads every coordinate as a pixel of that screenshot. On a display larger than 1568 px (or 1568 28-pixel patches) the coordinates a caller sends mean something different from before, so this is a major release.What changes for a caller of the tool
- —Each capture is fitted to
STANDARD_SCREENSHOT_LIMITS(Anthropic's standard tier, within OpenAI'sdetail: "high"budget) and numbereds1,s2, ….mouse_*,scrollandzoomcoordinates are pixels of the latest screenshot (or ofscreenshot_id) and are mapped onto the display; a coordinate outside the screenshot is refused. On a display that already fits, the mapping is the identity. To keep host pixels, passscreenshotLimitslarge enough that nothing is resized — but a model will then be sent an image its provider shrinks or rejects. - —Coordinate actions,
type_textandkeyare refused until the tool instance has taken a screenshot. Callscreenshotfirst. - —Results changed shape. An action returns
<label>: doneplus, by default, a new screenshot after 500 ms (settleMs,screenshotAfterActions: falseto turn it off), incontentwith a text block first;outputis no longer"ok".cursor_positionreturns text in screenshot pixels, not JSON in host pixels.datacarriesstepsandscreenshot(id,width,height,display). - —
screenshot,zoom,cursor_position,waitandlist_windowsare now read-only, so a review policy that exempts read-only calls no longer asks before them. Keep asking with an explicitaskrule forcomputer_use. - —
ActionInputgainszoom,wait,list_windows,focus_windowandbatch, and an optionalscreenshot_id; code that switches exhaustively overinput.typeneeds the new cases.
New
- —
createComputerUseTool(host, options)withscreenshotLimits,settleMs,screenshotAfterActions,maxBatchActions(20),maxWaitMs(10 000) andunavailableReason. - —
{ type: "batch", actions: [...] }: checked in full first, run in order, stopped at the first failure (which it names), one screenshot at the end. - —
zoom(full-resolution crop, throughcaptureRegionwhen the host declaresregionCapture),wait, andlist_windows/focus_windowwhen the host declareswindows. - —
computerUseUnavailableReason(provider): why a provider that cannot put an image in a tool result cannot drive the tool; pass it asunavailableReason. - —
screenshotTargetSize,STANDARD_SCREENSHOT_LIMITS,HIGH_RES_SCREENSHOT_LIMITSand the typesComputerUseToolOptions,ScreenshotLimits,ImageSize. - —New dependencies
fast-pngandpica(pure JavaScript and WASM, MIT; no native build), loaded only when a capture has to be resized.
- —Each capture is fitted to
- —
82769f1:
computer_usecan read a window's controls and act on them by reference, on a host that declaresuiTree— which the Windows backend of@namzu/computer-usenow does.What changes for a caller of the tool
- —Two new actions when the host declares
uiTreeand implementsuiSnapshotanduiAct:ui_snapshot { window_id? }returns the window's accessibility tree as text, each control that can be acted on with a ref such ase12;ui_act { ref, action, value? }performsinvoke,set_value,toggle,select,expand,collapse,focusorscroll_into_viewon it and returns a screenshot afterwards.ui_actmay be in a batch;ui_snapshotmay not. Refs count up across snapshots and only the latest snapshot's are accepted, so a stale ref is refused rather than acting on another control.ActionInputgains both, so code that switches exhaustively overinput.typeneeds two more cases — that is why this is a major release for@namzu/sdk. - —
ui_snapshotis read-only;ui_actis destructive. - —
list_windowsnow returns its window lines inside an untrusted-content frame (<namzu-untrusted kind="desktop-windows">), since titles are whatever each application shows. The lines themselves are unchanged; code that parsed the whole output text needs to skip the frame's three header lines. - —
createComputerUseToolreturns aComputerUseTool, aToolDefinition<ActionInput>withdescribeUiRef(ref)(experimental), which names the control a ref points at for a review screen. - —
UiSnapshotgains optionaltitleandapp;UiElement.refis empty for an element nothing can be done with. Both types stay experimental.
@namzu/computer-useOn Windows and WSL with the cua-driver backend,
capabilities.uiTreeistrueandSubprocessComputerUseHostoffersuiSnapshot(windowId?)anduiAct(ref, action, value?), read from cua-driver's UI Automation walk. A control is invoked in the background, without moving the pointer or bringing its window to the front; a field without a settable value is typed into when it is empty. An action whose driver died mid-request comes back as not done with an unknown outcome, never retried. The PowerShell fallback and the other platforms do not declareuiTree, so nothing changes there. - —Two new actions when the host declares
- —
82769f1: The interactive CLI now asks once per session before the model first sees your screen, and
strictmode no longer lets a screenshot run on its own — that second part is why this is a major release for@namzu/cli. Inprompt,accept-editsandplanmode the firstcomputer_usecall that would send the screen to the provider — a screenshot, a zoom, the list of open windows, a window's controls, or an action that returns a screenshot — opens a "Share your screen" box naming the provider; after a yes, later screenshots in that session run without asking, and clicks, typing andui_actare still reviewed as before. A mode switch keeps the answer; a new session (/new,/clear,/model) asks again.autonever asks.strictnow refuses a screenshot unless a rule allows it: to keep screenshots running instrict, addpermissions: { computer_use: 'allow' }(which also allows clicks) or a narrower rule for the actions you want.Approving a
ui_actcall shows the control by name (Press Button "Beş" (e30)), andui_snapshotas the window it reads.@namzu/sdk- —
ToolDefinition.capturesScreen?(input)anddefineTool({ capturesScreen })declare which calls send the operator's screen to the model provider.computer_usedeclares its observations and every action followed by a screenshot. - —Breaking: the authorization gate's
allow_read_onlyrule (allowReadOnlyTools: true, in every shipped preset) no longer allows a call that declarescapturesScreen; it goes to the review policy instead. WithcreateReviewHandlerand noscreenConsentnothing changes (a screenshot is approved as a read); a customResumeHandlernow seescomputer_usescreenshots, zooms, window lists and UI snapshots. To keep them out of review, add an explicit rule such as{ type: 'allow_by_name', toolNames: ['computer_use'] }(which allows clicks too). - —
createReviewHandler/createReviewPolicytakescreenConsent: { sessions: Set<string> }and an optionalcapturesScreen(name, input)predicate (default: the tool's declaration fromregistry). With them, the first such batch in a session is put topromptwith the newToolReviewRequest.screenConsent: true;strictrefuses it unless a rule allowed it,autodoes not ask, and without apromptit is refused with the newSCREEN_CONSENT_UNATTENDED_REFUSAL. A no isSCREEN_CONSENT_DECLINED_FEEDBACK. New typeScreenConsentRecord. WithoutscreenConsentnothing changes.
- —
Minor Changes
- —
82769f1:
ComputerUseHostcan now describe the display it captured and offer window and region operations. Everything is additive and optional, so an existing host compiles and behaves as before.- —
ScreenshotResult.display(DisplayInfo:id,x,y,width,height,scaleFactor,primary) says which display a capture shows. Set it in a host you maintain; without it the SDK assumes one display at the origin, the size of the capture, at scale factor 1. - —The contract is now written down as physical pixels everywhere: action points are relative to the display last captured, window bounds are virtual-desktop pixels. A host that clicks in logical units (points, or a DPI-unaware process) must convert at its own boundary.
- —Optional methods
listWindows(),focusWindow(id)andcaptureRegion(rect)are used only when the new capability flagswindowsandregionCapturearetrue.uiSnapshot()/uiAct()behinduiTreeare experimental and may change in a minor release. - —New exported types:
DisplayInfo,Rect,WindowInfo,FocusWindowResult,UiElement,UiElementAction,UiSnapshot,UiActResult.
- —
- —
28102e1: A scheduled run can no longer reach the scheduler through a shell the command-line reader does not follow.
powershell -c 'namzu schedule stop',pwsh -command '…',fish -c '…',tcsh -c '…'andbash.exe -c '…'were allowed whatever their text held: the scheduled-run floor took any shell at the head of a command with a-coption as read, but only the payloads ofsh,bash,dash,zsh,ksh,ashandmksh(andbusyboxrunning one) are. Such text now goes through the floor's tripwire like any other text a program runs as code. A job whose commands use those shells without naming the scheduler,NAMZU_HOMEor the browser profiles runs as before.SDK: new exports
nestedShellCommand(words),NESTED_SHELLSand the typeNestedShellCommand.nestedShellCommandsays, for one simple command's words (without its leading assignments), which-cpayloadlexShellCommandLinereads as a command line of its own, why it made the line opaque instead, ornullwhen it reads none. The lexer uses it itself, so its reading is unchanged. - —
28102e1: A scheduled run may now run a command whose code merely mentions namzu.
powershell.exe -NoProfile -Command "[System.Windows.MessageBox]::Show('Namzu: scheduled job running','Namzu')"was refused by the scheduled-run floor, because text passed to PowerShell,cmd, Python, Node or a shell reading its input was refused whenever it containednamzuorschedul. Such text is now refused only when it holds something that can reach the scheduler orNAMZU_HOME: aschedulesubcommand other thanlist,show,status,historyorlogswith the CLI or an expansion in reach, the service's name (namzu-scheduler…,com.namzu.…), a service tool with a namzu name or a*,pkill/killall,NAMZU_HOMEby name,.namzuas a path segment, a path into either protected folder, orLOCALAPPDATAwith anamzusegment. A job that was refused for naming the product runs; nothing that reached the scheduler before is let through (checked against bash on 35 159 generated lines, and on 1 189 labelled lines of PowerShell,cmd, Python, Node andshcode).The refusal now says which rule matched and where (
the argument `~/.namzu/x` names NAMZU_HOME (/home/you/.namzu),… it holds `schedule stop` (a `namzu schedule` subcommand other than …), in the argument `namzu schedule stop`) instead of listing everything the floor protects.SDK: a
predicateauthorization rule may carrydescribe(call), asked afterdecidereturned a decision; its answer is the gate's reason for that call instead of the rule's fixeddescription(anull, empty or thrown answer keepsdescription).describeRule(rule, call?)takes the call as an optional second argument. Both are additions; existing rules and callers are unchanged. - —
28102e1: In the TUI, the model's
scheduletool now lists every scheduled job, not only those of the session's folder. A model that created a job in a folder below the session's and then calledlistwas told "No scheduled jobs." whilenamzu schedule listshowed the job, because the TUI's host filtered by folder unless the model passedallFolders: true, which models do not. Jobs of the session's own folder are marked(this folder)and are still the only ones whose prompt the model sees.SDK:
ScheduleJobSummarygains an optionalinSessionFolder, which thescheduletool prints as(this folder)on the job'slistline. A host may use it to list every job regardless ofallFolders. The tool still askshost.list({ allFolders: false })unless the model sets it, so a host that filters by folder behaves as before. - —
28102e1: The model can change a scheduled job instead of deleting and recreating it. The
scheduletool has a new action,update:jobnames the job, and only the fields the model sets change (prompt,when,folder,tz,budget, orpermissionsas the whole new set, under the same limits ascreate). Asked whether it could change a job, a model with no such action deleted the job and created it again, and the job's history was lost.In the TUI the operator confirms every change on one screen: what changes first (the
-/+linesnamzu schedule editshows), aTHE PERMISSIONS CHANGEwarning when a run's rules,unmatched, execution or browser grant change, then the job as it will run.Cancelis the default.Savewrites the same job — its id, creation time and history kept — with the operator's new confirmation, keeps a paused job paused, and recordsedited by toolwith the changes in its history. A change is refused if the job was changed while the operator was being asked. Likecreate,updateis not preceded by the ordinary permission review inprompt,accept-editsandauto.SDK:
ScheduleToolHostgains three optional methods,previewUpdate(job, changes),confirmUpdate(request, signal?)andupdate(preview), and new typesScheduleJobChanges,ScheduleJobUpdateProposalandScheduleUpdateRequest. A host without them keeps working: the tool refusesupdatethere and tells the model not to delete and recreate the job. - —
28102e1: The
skilltool can tell the model which directory a skill's files are in (#536).New
createSkillTool({ resolveModelDirectory }), with the typesSkillToolOptions,SkillDirectoryResolver,SkillDirectoryRequestandSkillDirectoryContext.resolveModelDirectory(skill, { sandbox })receives the registered name and the directory the host reads the skill from, and returns the directory the model's tools can open, orundefinedwhen they cannot reach one. With it:- —a load opens with
[Skill directory: <dir>. …]on its first page and reportsdata.directory; when the resolver answersundefinedit says instead that the skill's directory is not reachable in this session and not to search the filesystem for it; - —the listing (
skillwithout a name) gives each skill'sdirectoryand no longer the registry'slocation, which is the host's path; - —
${CLAUDE_SKILL_DIR}inallowed-toolsexpands to the resolver's directory, and an entry using it is ignored when there is none.
The skill is still loaded from the registry's own path.
SkillTooliscreateSkillTool()and behaves exactly as before, so nothing changes unless you pass the option.SkillRegistryRef.catalog()entries may now carry an optionaldirectory, andSkillRegistry.catalog()sets it to the skill's directory; a registry that omits it still satisfies the interface. - —a load opens with
Patch Changes
- —
28102e1: The two errors that tell a model which tools it can use now give the same list: the tools the current step can call, meaning registered, active and allowed by
allowedToolsorprepareStep. Before, they could send a model back and forth between them until the run was stopped. A call refused asTool "X" is not available on this steprepeated the step's allow-list word for word. That list is taken when the request is built, so it could name a tool unregistered since (a connector that disconnected) or one that is deferred or suspended. A call to a name the registry does not hold listed the whole registry, including the tools the step refuses.That second error now reads
Unknown tool "X". Available: …instead ofUnknown or unavailable tool "X": Not found: "X". Available: ….(none)means the step can call nothing. This applies to a model's direct call, to a batch run without review preparation, and to a call a tool makes throughToolContext.dispatchTool(such asrun_code).ToolCallRepairContext.messageforunknown_toolcarries the new wording;availableToolsstill lists every registered tool. No exported types change. Code that parses the oldNot foundwording out of a tool result must look for the new wording instead. - —
82769f1:
computer_useclicks and drags with the left button when a call leavesbuttonout, instead of refusing the call (a model asked to click the Start button sent no button and spent a round trip on the refusal). Its description now tells the model to batch the steps it can predict but to look before typing into a window a step should have opened, and, on Windows, that a shell command is the surest way to start a program (Start-menu search on a non-English Windows turned an English program name into a web search in the browser).With a host that lists windows (the Windows cua-driver backend),
type_textandkeyare refused while a terminal is in front: in a real session a key chord that did not open the Run dialog sent "notepad" and ENTER into the user's own terminal and submitted what they were typing. A call that types into a terminal on purpose now fails with that reason; bring the target window to the front first, or run the command through a shell tool. With a host that reads controls, the first screenshot says so. No change is needed on your side. - —
28102e1:
claude-opus-5-5,claude-fable-5-1andclaude-mythos-5-1now have prices in the built-in catalogue. Before, their tokens were reported as unpriced. A turn that setturnConfig.costLimitUsdwithout its ownpricingtable was refused at the start on these models, or stopped withcost_unmeasurablewhen it reached one through a step or a fallback.The rates are the vendor's list prices per million tokens:
Model Input Output Cache read Cache write (5 min) claude-opus-5-5$4 $20 $0.20 $5 claude-fable-5-1,claude-mythos-5-1$10 $50 $0.25 $12.50 On these three models a cache read costs less than the usual 0.1x of input: 0.05x on Opus 5.5, 0.025x on the 5.1 pair. The catalogue has one write rate, the five-minute one the Anthropic driver requests, so the one-hour rates ($8 and $20) are not represented. Nothing to change on your side.
- —
28102e1:
claude-sonnet-5is priced at $2 input and $10 output per million tokens, with $0.20 for a cache read and $2.50 for a five-minute cache write. The catalogue had $3 / $15 / $0.30 / $3.75. That was the increase the vendor scheduled for 2026-09-01 and then cancelled; $2 / $10 is now its standard price.What changes for you: a Sonnet 5 turn reports two-thirds of the cost it reported before. A
turnConfig.costLimitUsdset against Sonnet 5 now allows about 1.5 times as many tokens before it stops the turn. To keep the old ceiling, lower the limit to two-thirds of its value, or pass your ownpricingtable.
- —
Minor Changes
- —
2b96136: Add the browser tool contract, let
argument_patternrules ask for review, and let a scheduled-job proposal carry a browser grant.Browser tools (new).
createBrowserTools(host)returns two tools over aBrowserHostyou supply:browser(navigate, back, forward, reload, snapshot, screenshot, scroll, wait_for, tabs) andbrowser_act(click, type, fill_form, select, press, hover, upload, dialog). The SDK ships no browser engine. Everybrowser_actcall carries a requiredorigin, and the host contract says the host compares it with the live page before acting.urlandoriginare canonicalised by the input schema (WHATWG form, punycode, no default port, no trailing dot on the host). Other schemes, credentials in the URL and cloud metadata addresses in any spelling are refused. Because the gate sees the canonical value, a site rule matches the address the browser loads. Page text is returned insidewrapUntrusted, under aPage: <origin> — "<title>" (tab tN)header. Hosts refuse by throwing structural shapes (browser_origin_mismatch,browser_stale_ref,browser_human_required,browser_outcome_unknown,browser_site_denied). New exports:createBrowserTools,BROWSER_TOOL_NAME,BROWSER_ACT_TOOL_NAME,isBrowserCallReadOnly,browserHostErrorOf,formatBrowserPageHeader,canonicalizeBrowserUrl,canonicalizeBrowserOrigin,canonicalizeBrowserSitePattern,isCloudMetadataHost,BROWSER_URL_MAX_LENGTH,BROWSER_SNAPSHOT_MAX_CHARS,BROWSER_WAIT_MAX_MS,BROWSER_FILL_FORM_MAX_FIELDS, and the types in their signatures (BrowserHost,BrowserCapabilities, the action, result and error types). Both tools arecategory: 'network'. Do not pair them with a preset that allows network tools by category because a sandbox confines egress: the browser runs outside the sandbox.argument_patternwithdecision: 'review'(widened union).AuthorizationRule'sargument_patternvariant now accepts'review'. It matches asdenydoes: the whole value, then every segment of a command line. Code that switches exhaustively over that variant'sdecision, or narrows it to'allow' | 'deny', needs areviewbranch. Existing rules behave as before.ToolDefinition.urlArgument(new, optional). A tool whose input schema canonicalises an argument to a URL can declare it. Anargument_patternrule on that argument then tests the value whole, instead of cutting it at&,;or|as a command line, which made anallowfor a site decline every address with a query string. Tools that do not declare it are unchanged.defineToolaccepts it too.Reason text. A
custom_patternrule withdecision: 'review'is now described assent for review by a pattern rule …; it used to sayallowed by …. If you match on the old text, update the match.Scheduled jobs.
ScheduleJobDraft.permissions.browser?: ScheduleBrowserGrant(profile,sitesmapping each site toread,askoract,headed?) andScheduleToolHost.browserGrants?: booleanare new. Thescheduletool refuses a proposed browser grant unless your host setsbrowserGrants: true, so a host that does not know the field keeps working unchanged and never silently drops a grant. It canonicalises site keys and refuses*. It also treats the grant as network access, so the grant is refused beside a shell on the host. The refusal for web access beside a host shell now says "web or browser access".See
docs/sdk/browser-tools.mdanddocs/sdk/review-policy.md#rules-that-ask. - —
2b96136: A generic tool result view may now set
outcome: 'cancelled'for a call the person declined on the tool's own screen.save_skilland thescheduletool'screate,resumeanddeleteuse it (thescheduletool also setsdata.cancelledon those results), and the TUI shows the row as○ … Cancelled — nothing was savedinstead of✗ … failed: Error: The operator cancelled. The model still receives the same refusal text. - —
2b96136: A tool call a person declines without giving a reason now tells the model not to get the same content or result another way — another tool, another site or address, or a web search — unless it asks first and the person agrees. The text was "User declined to run the proposed tool(s)." (and "The user rejected this tool call." on a resumed decision); in a live session a model whose browser navigation was declined fetched the same page through web search instead.
New export:
DECLINED_TOOL_CALL_FEEDBACK, the text. A host that passes its ownfeedbackwith a refusal is unchanged. A test or host that matched the old default text must match the new one. - —
d233f26: Add a
predicateauthorization rule, and export the command-line lexer the gate uses.- —
AuthorizationRulegains{ type: 'predicate', description, decide }.decide(call)receives the tool name, input, definition and thecommandDialectthe line runs in (shwhen the caller did not say) and returnsallow,deny,reviewornull; it sits in its place in the rule list, after the dangerous-command floor. Adecidethat throws is read asdeny. New typesAuthorizationPredicateandAuthorizationPredicateCall. - —
lexShellCommandLineis exported, withShellLexResult,ShellCommand,ShellWord,ShellRedirectionandShellLexOptions, so a predicate reads a command line exactly as the gate does. - —A lexer result also reports
compoundWords(afororselectloop's variable and list, acasestatement's subject and patterns) and, on a here-document redirection, itsbody.
Nothing existing changes behaviour.
AuthorizationRuleis also the type ofGateEvaluationResult.matchedRule: code that switches overrule.typewith an exhaustive check (const x: never = rule) needs apredicatecase or adefault:branch to compile. - —
- —
2b96136:
ScheduleToolHost.create()may return an optionalnote, which thescheduletool appends to what the model is told about the new job. Existing hosts that return only{ name }are unaffected. The CLI uses it to tell the model that no scheduler is installed, so its reply no longer promises a run that cannot happen until you runnamzu schedule install. - —
d233f26: Add a schedule time engine and evaluator, and the
scheduleandsession_looptools.New exports, all additive:
parseScheduleSpec,parseCronExpression,nextFireTime,previousFireTime,upcomingFireTimes,countOccurrences,describeSchedule,validateTimeZone,hostTimeZone,parseDuration,evaluateJob,ScheduleValidationError,SCHEDULE_CATCH_UP_WINDOW_MS,SCHEDULE_LATE_GRACE_MS,buildScheduleTools,buildSessionLoopTools,scanSchedulePrompt,revealHiddenCharacters,SCHEDULE_TOOL_NAME,SESSION_LOOP_TOOL_NAME,generateScheduleJobId,generateScheduleRunId, and the types in their signatures (ScheduleSpecand its three variants,CronExpression,ScheduleEvaluationInput,ScheduleDecision,ScheduleSkipReason,ScheduleMissedReason,ScheduleToolHost,ScheduleJobDraft,ScheduleJobPreview,SessionLoopHost,SessionLoopand their neighbours).Everything here is pure:
Intlfor time zones, no clock, no I/O, no new dependency. Cron follows cronie's DST rule (a fixed-time job fires once across a change; a wildcard job fires at every matching instant). Nothing existing changes.ScheduleDecision,ScheduleSkipReasonandScheduleMissedReasonmay gain members in a later minor release, so switch over them with adefault:branch. Seedocs/sdk/schedules.md. - —
2b96136:
loadSkill(and soSkillRegistry) skips frontmatter keys it does not read, whatever YAML they are written in, instead of refusing the whole file: aSKILL.mdcarryingargument-hint: [file]or ahooks:block with a list now loads with those fields ignored. The keys it does read are still parsed strictly; they are exported asSKILL_FRONTMATTER_KEYS.disable-model-invocation: truenow reads asinvocation: operator; a value other thantrueorfalse, or one that contradicts an explicitinvocation, refuses the file.parseFrontmattertakes an optional third argument,{ readsKey }, to get the same leniency for a caller's own vocabulary; without it every key is parsed and refused as before. A skill file that used to be refused over an unread key now loads, and one withdisable-model-invocation: trueis no longer offered to the model — nothing else changes. - —
2b96136: A tool can now stop the turn for a person: return
handoff: { kind: 'human-required', reason, detail? }on itsToolResult(new typeToolHandoff). The kernel commits the batch's results, writes a checkpoint and ends the segment withturn_pausedinstead of calling the model again. The event and its session-log record carry the new optionalhandofffield, and the SSE (turn.paused) and A2A bridges forward it.resumeSessioncontinues the turn from that checkpoint as it does after a provider pause, with a model call that sees the results. In a delegated child (a turn withparentSessionId) the handoff fails the child's turn with a non-retryabletool_errornaming the reason, so the parent receives a failed child result.Nothing changes for tools that do not set the field. A consumer that switches exhaustively on
turn_pausedfields, or validates session-log records with its own strict schema, should accepthandoff.
Patch Changes
- —2b96136: The
browsertool'scursorfield tells the model it is only thenextCursora previous snapshot returned, to be left out for the top of the page, and never a URL. A model passed the page's address as the cursor and got a refusal before it could read the page. - —2b96136: A
browser_human_requiredrefusal from a browser host now pauses the turn. The browser tools setToolResult.handoff(reasonin words such ashttps://github.com is showing a sign-in page;detailcarriestool: 'browser',cause,origin,profileandloginCommand) as well asdata.handoff, so the kernel stops before the next model call and waits for the person, as it does for any tool handoff. Before this, the model read the refusal and carried on. Nothing changes for other refusals. - —2b96136: The coding-agent working doctrine now tells the model never to change git configuration (
user.name/user.email, hooks, remotes, credential helpers) or other persistent settings without asking. When a commit fails because no identity is set, it tells you the command instead of inventing an identity. - —2b96136: The
scheduletool's input schema now says thatbudgetlimits one run and thatmaxIterationscounts model steps, not repetitions of the job. A model proposedmaxIterations: 1for a job meant to post once per run, and every run stopped after its first model call. The CLI's confirmation of a job, on a terminal or in the TUI, warns when it allows fewer than 10 iterations. - —2b96136: The
scheduletool asks the model to leave optional fields (folder, time zone, execution, budget, a visible browser window) unset unless the user asked for them, and the TUI's confirmation of a proposed job marks every such value the model set that differs from what you would get by default, e.g.Chosen by the model, not the default: time zone America/New_York, not this machine's Europe/Istanbul. - —2b96136: A job the model proposes, resumes or deletes in the TUI is confirmed once, on the job's own screen. The permission review no longer asks "Do you want to run schedule?" first in
prompt,accept-editsorauto.planandstrictstill refuse the call, aschedulerule ofaskordenystill applies, andpauseis still reviewed. The SDK'sscheduletool no longer declaresdeletedestructive, since the host confirms it before anything is removed; a host that relied on that flag to review deletes should add anaskrule forschedule. - —2b96136: The
scheduletool no longer refuses a proposal that uses theread-onlypreset with web or browser access as "a shell on the host". The preset deniesbash, but the check looked only at explicit rules andunmatched, so{ preset: 'read-only', unmatched: 'park', browser: … }was refused. A proposal withedit-in-folder, or with abashrule that is notdeny, is still refused. - —2b96136: The
scheduletool's input schema describesbudget.tokenBudgetas what the whole run may spend, with every model call resending the prompt, and the CLI's confirmation of a job warns when it allows fewer than 50 000 tokens. A model proposed 4 000 tokens for a browser job whose runs each took about 110 000. - —2b96136: The
skilltool now presents its calls asRead skill <name>(orList skills) and hides a successful result, so a host shows one row instead of the raw input and the skill's body. In the TUI, the Working row saysWaiting for youwhile the screen that saves a skill is open, instead of counting on as if the turn were working.
- —
Major Changes
- —
2e2ea14: A skill's
allowed-toolsnow pre-approves its tools. It no longer restricts the tool set.The field comes from the Agent Skills format, and that format defines it this way: the listed tools skip the approval prompt for the rest of the turn that loaded the skill, and every other tool stays callable. Namzu read it the other way. After the
skilltool loaded a skill, the next batch was narrowed to the listed tools and the model was told to "restrict yourself to" them. A skill withallowed-tools: Read Greptherefore tookbashaway, and the model stopped doing the work.What breaks in
@namzu/sdk- —A loaded skill no longer narrows
ToolContext.allowedTools. If a host relied onallowed-toolsto confine the model, it should narrow the turn itself withQueryParams.allowedTools, a step'sallowedTools, ordenyrules. - —
ToolContext.adoptSkillScopeis deprecated. The kernel never supplies it, so a tool that calls it through?.now does nothing. It will be removed in the next major. UseToolContext.grantSkillTools. - —
createReviewHandler/createReviewPolicyinpromptandaccept-editsmodes now approve a batch without asking when every call it would ask about is covered by a skill loaded earlier in the turn. To keep asking about every call, passskillGrants: 'ignore'.planandstrictstill refuse such calls, and an operatordenyoraskrule, a destructive call, or a path outside the roots or the sandbox is never covered. Each approval made this way is written to the audit trail under the skill's name. - —The
bashtool runsbash -cinstead of/bin/sh -cwherever bash exists (the firstbashonPATH, then/bin/bash, then/usr/bin/bash), including for background jobs. On a host whose/bin/shisdashorbusybox sh(Debian, Ubuntu, Alpine), commands now run in bash; a command that relied ondashbehaviour, such asechoexpanding\n, behaves as bash does.BASH_ENV,ENV,SHELLOPTS,BASHOPTSandBASH_FUNC_*are removed from its environment. To keep/bin/sh, setNAMZU_BASH_SHELL=/bin/shin the environment of the process that runs the SDK. In a sandbox the tool now passes the guest/bin/sh -c '<launcher>' sh '<command>', which runs bash when the guest has it; a customSandbox.execorspawnDetachedthat inspected its arguments for['-c', command]sees the launcher instead. With no bash, the host still runs/bin/sh -c. - —A command line is read for the shell that runs it.
AuthorizationGate.evaluate,evaluateRuleandSkillGrantSet.coveringSkillcalled without a dialect read it for any POSIX shell (sh), so an allow rule orBash(<pattern>)entry no longer approves a line using a bash-only construct ($'…',&>,|&,<<<,[[, arrays, brace expansion,timeand others) unless the caller says the shell is bash. The kernel says so for thebashtool on a host that has bash; inside a sandbox it reads insh, because the guest may not have bash. A host calling the gate itself passescommandDialect: 'bash'in theToolCallContext(or{ commandDialect: 'bash' }as the last argument ofevaluateRuleandcoveringSkill) when the command will run in bash. Deny rules are unaffected: they still see every command. - —
parseAllowedToolssplits on whitespace as well as commas, and keepsTool(pattern)entries whole."read write edit"used to be one name and is now three. - —The skill manifest in the system prompt renders the field as
<pre_approved_tools>instead of<allowed_tools>. Theskilltool's notice lists what was pre-approved and what was ignored, and says every other tool remains available.
Added:
ToolDefinition.commandDialect(and thedefineTooloption),ToolCallContext.commandDialect,EvaluateRuleOptions, theShellDialecttype, andNAMZU_BASH_SHELL;ToolContext.grantSkillTools,ToolCallSummary.skillGrant,approve_tools.skillGranted,ReviewPolicyOptions.skillGrants,SkillGrantSet,compileSkillGrant,SKILL_TOOL_NAME_ALIASES,permissionPatternToRegExpSource, and aFrontmatterOptionsthird argument toparseFrontmatter(lists), which the skill loader uses so thatallowed-toolscan be a YAML list. Names are matched case-insensitively and through the format's aliases (Read→read,WebFetch→web_fetch).Bash(git status *)uses the CLI permission-table glob, applied to each command in the line.${CLAUDE_SKILL_DIR}and${NAMZU_SKILL_DIR}expand to the skill's directory. An unknown name is ignored and reported, and never widens the grant. A tool that is destructive for every input (the shippedwriteandrun_code) is ignored and reported too, because each of its calls is reviewed anyway.BashOutput,KillShell,TaskOutputandTaskStopmap tojob, andTaskCreate,TaskUpdateandTaskListtotask_*.ToolContext.grantSkillToolsreturns acommit(), and theskilltool records the grant only once it has delivered the skill's instructions. The grant also ends when the operator sends a message while the turn is still running (inboundMessagesor steering), through the newSkillGrantSet.clear(). ABash(<pattern>)entry never covers a line that redirects output into a file (git status > ~/.bashrc);/dev/nulland2>&1stay covered. A whole-toolBashentry grants the tool as it is, writes outside the working directory included, because bash has no path argument for the escalation check. An entry naming a tool thatallowedToolswithholds from the turn or step is ignored as not available, andSkillGrantToolResolvergains an optionalunavailablefield for that.@namzu/cli: a plugin skill'sallowed-toolspre-approves for the turn and no longer takes tools away.SKILL.mdfiles whoseallowed-toolsis a YAML list are now listed instead of refused. The[permissions]glob now comes from the SDK, and it matches the same commands as before.permissionChecksread a builtin tool's command line in the dialect that tool reports, as the runtime does. - —A loaded skill no longer narrows
Patch Changes
- —
2e2ea14: Permission rules on a command line now read it the way bash does. A
denyrule catches commands it used to miss, and a few lines that used to be approved or refused by accident are now decided on what they run.An
argument_patternrule on a command argument, a skill'sBash(<pattern>)entry and the check that such an entry never covers a write through redirection used to rely on three separate readers of bash quoting, which disagreed in places. WithBash(git status *)granted,git status $'\'' ; touch pwned #'andgit status $'\'' > ~/.bashrc #'were pre-approved, because$'\''was read as a closed quote and an open one. One lexer now serves all three (seedocs/sdk/command-lines.md). It was checked against bash 5.2 and 5.3 on 2.9 million generated lines with no disagreement.What changes for a host:
- —A
denyrule also matches each command's words after quote removal.^git pushnow denies'git' push,g\it push,$'\x67it' push,GIT_DIR=. git push,bash "-c" "git push"(the quoted-cis still the flag) andbash -lc 'git push'. None of these were denied before. - —A line whose only quoting is an ANSI-C quote is decoded rather than refused: an
allowrule or aBash(<pattern>)entry that matchesgit status $'-s'now approves it, since it runsgit status -s.> $'/dev/nul\x6c'is/dev/nulland is not a write. - —A here-document body is data, not commands, so
cat <<EOF … EOFis matched ascat <<EOF. - —A line is refused by
allow(opaque) in some cases it used to approve: a syntax error,[[ … ]], arithmetic on a variable ($((x))),${!x}, a function definition,shopt/set -o posix,timefollowed by an option (bash as/bin/shruns the commandtimethere), and two forms bash itself reads two ways ("$${…"in double quotes, and a quoted or expanding>&target, whose substitution bash 5.2 runs). They now go to review. - —A segment no longer carries a trailing comment:
git push #'isgit push. - —An
argument_patternrule on an argument the tool declares as itspathArgumenttests the whole value and does not read it as shell, sosrc/app/(auth)/page.tsxis not refused as a syntax error.
No configuration change is needed.
- —A
- —
Minor Changes
- —4375e72: A provider-hosted web search now says what it searched for.
StreamChunk.delta.hostedTool, and so thehosted_toolsession event and thehosted.toolSSE event, may carryquery(the search query),url(a page the provider opened instead of running a query) andresults(how many sources it reported). Codex fills them from the search call's action, Anthropic from the search block's streamed input and its result list, Google from the grounding metadata. Each field is optional and absent when the provider does not say; nothing that already readsid,nameandstatuschanges. A custom driver can start emitting them whenever it knows them.
Patch Changes
- —1b65e2e:
CODING_AGENT_ORCHESTRATE_DOCTRINEgains one sentence: agents in the same phase are launched in the same response, withrun_in_background: truewhen they are to be waited for together, never one at a time. The text only reaches a prompt when a host turns orchestrate mode on (codingAgentDoctrineContribution({ orchestrate: true })); with the option off the rendered doctrine is byte-identical to before. A host that snapshots the orchestrate text will see the new sentence.
- —4375e72: A provider-hosted web search now says what it searched for.
Minor Changes
- —
9bd0c99: Plan mode now holds inside delegated work.
@namzu/sdk: new optionalBaseAgentConfig.reviewAllowedCallsandAgentTaskContext.reviewAllowedCalls.AgentManagerstamps the spawning context's function onto every child (after theconfigBuilderruns) and onto the child's spawn context, so grandchildren inherit it too;SupervisorAgenthands its own to its workers, andReactiveAgentandSupervisorAgentpass it toquery(). Before, only the turn a host calledquery()with sawQueryParams.reviewAllowedCalls: inside a child, a batch a rule allowed, or one an approval earlier in the child's turn covered, ran without reaching the borrowed review handler. Nothing changes for a host that never sets the field. A child config that sets its own value keeps it, but it is OR-ed with the inherited one: a child can ask for more review and can no longer answerfalseover a parent that answerstrue. A host that builds its ownAgentTaskContextfor aTaskSchedulershould set the field from the function it passes its ownquery().@namzu/cli: plan mode entered with Shift+Tab while a sub-agent runs now refuses that sub-agent's next change even when apermissionsrule allows it; before, the child ran it./resumeon a parked turn now continues it under the permission mode the operator is in, read at each decision like a new turn's, for the turn and its sub-agents; before, the interactive terminal resumed it underautowhatever the mode, so/resumein plan mode let its changes through and a Shift+Tab during it did nothing. With nobody asked on a resumed turn,promptandaccept-editsstill approve what the rules leave to review;planandstrictnow refuse.namzu execresumes under the mode it was started with, as before, and in plan mode that now also covers changes a rule allows.
- —
Minor Changes
- —
51af1b3: Plan mode now refuses a change that a permission rule allows. Before, a batch whose every call a rule allowed (for example
permissions: { bash: 'allow' }) ran without reaching the review handler, so a turn in plan mode, whether it started there or the operator entered it with Shift+Tab mid-turn, still rantouchor any other allowed command, while the model had been told that every change is refused. The CLI now asks the kernel to send such batches to review while it is in plan mode; nothing changes in any other mode.For SDK hosts:
QueryParams.reviewAllowedCalls?: () => booleanis new and optional. Read once per batch;truesends a batch a rule allows, or a grant from earlier in the turn covers, toresumeHandlerinstead of running it, each call carrying the gate's decision inauthorization. A rule'sdenystill refuses. Absent, behaviour is unchanged. - —
406e7a2: A task removed with
task_updatestatusdeletedis now distinguishable on the stream: itstask_updatedevent carriesdeleted: true(the SSEtask.updatedevent too), with the subject and status the task had when it went. Before, a removal arrived as an update that changed nothing, so the interactive terminal kept drawing the removed task as an open step in the checklist. It now drops the task and writesRemoved task · <subject>. The field is optional and absent on every other update; a consumer that ignores it sees the same events as before.
Patch Changes
- —0d23de8: The planning tools
task_create,task_updateandtask_listnow present themselves in words to a host:Add task · <subject>,Start task,Complete task,Check tasks, a hidden result view when a call succeeds, andTasks · N/M donefor a listing. No presented view carries a task id, an owner or the JSON arguments any more; a host that renders the generic presenter used to showTask update({"id":"01a0…","status":"completed"})aboveTask 01a0… updated — status: completed. The model-facing output still names the id it needs.task_createandtask_updateaddsubjectto their resultdata, andtask_listsays1 task:rather than1 tasks:. Nothing to change on upgrade unless a host matched the old presented strings.
- —
Major Changes
- —
9355755: Checkpoint retention is part of every checkpoint store, and a failed prune no longer ends a turn.
- —
SessionCheckpointStore.prune(scope, keepLast)is required. It deletes a turn's oldest committed checkpoints untilkeepLastnewer ones remain and returns the deleted ids, which the kernel records ascheckpoint_pruned. It never deletes a checkpoint an open decision references, and it counts and deletes only checkpoints acheckpoint_writtenrecord commits. A custom store implements it; the optionalpruneCheckpointsit replaces is gone.selectSessionCheckpointsToPruneis the selection the built-in stores use. - —A prune that throws after an iteration's checkpoint is logged
(
Checkpoint retention failed; older checkpoints are kept for now) and the turn continues. Before, the error failed the live turn. - —Listing parked work no longer reads checkpoints at all:
SessionIndex.listPendingDecisionsandlistTurnsanswer from the index.
- —
- —
755a81a: Consolidation no longer writes the same learning twice.
consolidationEntrynow adds aknowledge:<digest>tag,metadata.knowledgeDigestandtype: 'project', the digest covering the turn's decisions, discoveries and failures. Before writing, the runtime calls the newisConsolidated(store, entry)and skips the write — and itsmemory_consolidatedevent — when a consolidation with that digest already exists, archived included. This is the deduplicationcreateMemoryPromoteralready did.Why major: the duplicate records were a defect — search filled with copies of one learning — but a documented event and a store write that used to happen on every settled turn with
consolidateIntonow do not happen for a turn whose learnings match an earlier one. A host that countedmemory_consolidatedevents or records per turn observes that as a changed default, whatever the reason for it.What changes for a host passing
consolidateInto: a turn whose learnings are identical to an earlier consolidation's writes no record and emits nomemory_consolidatedevent. A host that expected one record per turn should look for the earlier record by itsknowledge:<digest>tag. A host comparingconsolidationEntry(...).tagsexactly sees one more tag. There is no switch to restore a write per turn; a host that needs one writes the entry itself withstore.create(consolidationEntry(...)). - —
cb1f00c: A session held in memory stays in memory: its checkpoints, its token ledger and the child sessions it delegates to. Before, such a session kept only its evidence in memory and wrote its ledger and checkpoints to disk, one tree per call with nothing that removed it.
What changes for you.
- —A
query()(or agent) whosesessionLogis anInMemorySessionLogand which names nopathsand nocheckpointStorekeeps its checkpoints and ledger in memory, with the log. A later call with the same log can resume the same turn. A host that wants those files on disk passespaths(aSessionPaths) or its owncheckpointStoreandtokenBudgetStore. - —With no
tokenBudgetStore, the ledger lives where the checkpoints live. A host that passes anInMemorySessionCheckpointStoreand later copies its checkpoints into a new one to resume must pass the sameInMemorySessionTokenBudgetStoreto both calls; otherwise the resume fails because the ledger its checkpoint names is missing. - —
BaseAgentConfigtakessessionLogandcheckpointStore, whichReactiveAgentandSupervisorAgentforward toquery(). ASupervisorAgentheld in memory setsAgentTaskContext.childStorage(ChildSessionStorage), andAgentManagerthen gives each delegated child its ownInMemorySessionLog(and the supervisor'scheckpointStore, if it named one) unless the child's config names asessionLogorpaths. Such a child writes nothing underNAMZU_HOME. A host that pairsquery()with its ownLocalTaskSchedulersetschildStorageon the context it builds.
InMemorySessionTokenBudgetStoreis a process-localSessionTokenBudgetStorethat refuses the same regressionsDiskSessionTokenBudgetStorerefuses. - —A
- —
933ba6d:
read_memory's output is no longer the memory body alone for text and Markdown records.For a store that implements
getRecord—DiskMemoryStore,InMemoryMemoryStoreandMarkdownMemoryStoreall do —read_memorynow appends, after the body, a---line, then when the memory was last updated and how old it is, a notice to verify named files and functions when it is older than today, and each[[name]]link resolved. This is what lets the model see that a memory is a point-in-time claim.What breaks: a host that displayed or parsed
read_memory'soutputas the stored body sees the footer after it. A record saved withformat: 'json'is unaffected — its output is still exactly the stored text and still parses — so only prose records change.What to do: take the body from the store (
store.get(id)) rather than from the tool's output, or strip everything from the last\n\n---\n.datacarriesupdatedAt,name,typeand the resolvedlinksfor every format. - —
3e7a97b: The run is gone. The kernel's model is now session → turn → message: a session is one conversation with one agent, a turn is one unit of work inside it (a prompt, a goal round, a resident step, a verification step), and a subagent is a child session. Each session is recorded in one append-only, hash-chained JSONL log under
NAMZU_HOME, and that log is the source of truth; SQLite is only an index rebuilt from the logs. Seedocs/sdk/session-log.mdfor the record schema.Before you upgrade: resolve or abandon parked runs on 43.x. Run
drainRuns(orresumeRun) on the old version until nothing is parked. This version cannot read a run tree, aRunState, an old checkpoint or an old token ledger, so a run still parked when you upgrade cannot be resumed. There is no migration.What breaks
Identity and ids.
- —
RunId,generateRunId,asRunId,parseRunIdandisEntityId(v, 'run')are removed. UseTurnId,generateTurnId,asTurnId,parseTurnIdand'turn'. A newRecordIdnames a log record. - —Every
generate*Idmints a UUID version 7 instead of v4, so ids sort by creation time as strings. Every id check still accepts versions 1 to 8. - —Anywhere a
runIdfield named the owner of something, it is nowsessionId, plusturnIdwhere the thing belongs to one turn:ToolContext(third-party tools readcontext.sessionIdandcontext.turnId), the context a guardrail receives,StepResult,PrepareStepContext,AnswerReviewContext, probes and provider instrumentation, deliverables, plans and approval requests, bus owners, background-job owners, tasks, working memory andHandoffLockRejected.details(turnId: TurnId).parentRunIdbecomesparentSessionIdplusparentTurnId.
Core types (rename, same meaning unless noted).
Removed Use Run,AgentRun,AgentSessionTurn,AgentTurn(TurngainssessionId;replayOfbecomesforkedFrom)RunExecutionStatus,RunStatus,deriveRunStatusTurnExecutionStatus,TurnStatus,deriveTurnStatus(same members)RunCancelledTurnCancelledAgentRunConfig,RunConfigSnapshot,RunMetadata,RunStateMetadataTurnConfig,TurnConfigSnapshot,TurnMetadataRunPersistence,RunPersistenceConfig(.runConfig)TurnRecorder,TurnRecorderConfig(.turnConfig;pruneKeepLaststays on it)RunEvent,RunEventListener,PersistedRunEvent,RUN_EVENT_SCHEMA_VERSION,RunEventSchemaVersionSessionEvent,SessionEventListener,SessionRecord,SESSION_RECORD_SCHEMA_VERSION,SessionRecordSchemaVersion(the event fieldschemaVersionbecomes the record fieldv)RunEventCursor,RunEventReplay,RunEventReplayRefusal,RunEventLogHead,resolveRunEventReplaySessionLogCursor,SessionLogReplay,SessionLogReplayRefusal,SessionLogHead,resolveSessionLogReplayRunState,RUN_STATE_VERSION,parseRunState,RunStateVersionError,captureRunState,loadRunState,RunStateScopeTurnState,TURN_STATE_VERSION(1),parseTurnState,TurnStateVersionError,captureTurnState,loadTurnState,loadSelectedTurnState,TurnStateScope.parseTurnStaterefuses aRunStateby name.resumeRun,ResumeRunParamsresumeSession,ResumeSessionParams(same outcomes:no-checkpoint,awaiting-decision,resumed)claimRun,releaseRun,RunLease,ClaimRunOptionsclaimSession,releaseSession,SessionLease,ClaimSessionOptionsdrainRuns,DrainRun,DrainRunsParams,DrainRunsResultdrainParkedTurns,DrainTurn,DrainTurnsParams,DrainTurnsResultlistDurableRuns,paginateDurableRuns,toDurableRunEntry,DurableRunEntry,DurableRunOrder,DurableRunPage,ListDurableRunsOptions,CheckpointListingScope,assertContiguousListingScopeSessionIndex.listTurnsandlistPendingDecisions;DurableTurnEntry,DurableTurnOrder,DurableTurnPage,ListDurableTurnsOptionsDelegatedChildRun,ChildRunStorage,RunHierarchyNodeChildSessionSummary(SessionIndex.listChildren),ChildSessionStorage,SessionTreeNodereplayRun,RunSummaryreplayAudit,AuditSummary. Audit entries areauditrecords in the session log;AuditEvent.runIdbecomesturnId?.RunQuery,RunQueryOptions,RunTranscriptUnavailableError,RunTranscriptUnavailableReasonSessionQuery,SessionQueryOptions,SessionTranscriptUnavailableError,SessionTranscriptUnavailableReasonRunReporter,createRunReporter,recordRunDurationTurnReporter,createTurnReporter,recordTurnDurationRunApprovalPolicySessionApprovalPolicy(ApprovalPolicyis unchanged and still the{ name, handler }value)RunEvidence*,RunTextEvidence*,DiskRunEvidenceOptions,createDiskRunEvidenceSource,createDiskRunTextEvidenceSourceSessionEvidence*,SessionTextEvidence*,SessionEvidenceSourceOptions({ scope, logPath, … }),createSessionEvidenceSource,createSessionTextEvidenceSource;ToolContext.captureRunEvidencebecomescaptureSessionEvidence. Its source's scope is the session, not the turn: it reads every earlier turn in the session log and the running turn up to its latest record (the old capture read the running run's own transcript and nothing else). A reader for one turn only iscreateSessionTextEvidenceSourcewithscope.turnIdover the session log.RUN_STATUS_READ_MODEL_ID,RunStatusReadModelOptions,RunStatusState,createRunStatusReadModelSESSION_STATUS_READ_MODEL_ID,SessionStatusReadModelOptions,SessionStatusState,createSessionStatusReadModelSharedRunWorkspace*,RegisterSharedRunPlanInputSharedSessionWorkspace*,RegisterSharedSessionPlanInputBidiRun,BidiRunParams,BidiRunEvent,startBidiRunBidiTurn,BidiTurnParams,BidiTurnEvent,startBidiTurnderiveRunOptions,DeriveRunOptionsInputderiveTurnOptions,DeriveTurnOptionsInputEvalRun,evalRunFromQuery,evalRunFromRunEvalTurn,evalTurnFromQuery,evalTurnFromTurnRunMemoryCandidate,RUN_MEMORY_TAGSessionMemoryCandidate,SESSION_MEMORY_TAGSubsessionSpawnedEvent,SubsessionMessagedEvent,SubsessionIdledEvent,SubsessionLifecycleEventChildSessionSpawnedEvent,ChildSessionMessagedEvent,ChildSessionIdledEvent,ChildSessionLifecycleEventReadRunEventsOptionsReadSessionLogOptionsCheckpointRunScopeCheckpointScope({ tenantId, projectId, sessionId, turnId })ReplayAttribution(Run.replayOf)TurnForkOrigin(Turn.forkedFrom,{ sessionId, turnId, checkpointId })validateTokenBudgetSnapshot,OpenTokenBudgetOptions,DiskTokenBudgetStoreConfigvalidateSessionTokenBudgetSnapshot,OpenSessionTokenBudgetOptions,DiskSessionTokenBudgetStoreOptionsTurnStatusResolver.blockingRun, the handoff deps fieldrunStatusblockingTurn,turnStatus(onSingleHandoffDepsand the broadcast deps)Agent.forRun(),AbstractAgent.forRun()forTurn()AgentHandle.queueForNextRun()queueForNextTurn()CaseResult.runCaseResult.turnAdvisoryBudget.maxCallsPerRun,maxCostPerRunmaxCallsPerTurn,maxCostPerTurn(the advisory stack is built per turn, so the bound always was)query(),drainQuery()andrunAgent().query()is nowAsyncGenerator<SessionEvent, Turn>anddrainQuery()resolves to aTurn.QueryParams.runConfigisturnConfig.runStore,pathBuilder,claimFence,emergencySaveandsupersedesEmergencySaveare gone (ReactiveAgentConfig.emergencySavegoes with them); passpaths(aSessionPaths),sessionLog,checkpointStore,lease,budgetandtokenBudgetStoreinstead, andturnIdwithresumeFromCheckpointto continue a turn.RunAgentResult.runis renamedturn;runAgent,RunAgentOptionsandRunAgentResultkeep their names.One active turn per session. Starting a turn while the session has one running, paused or interrupted throws
TurnInProgressError(sessionId,activeTurnId,state);isTurnInProgressErrorrecognises one across package copies. A paused turn is continued withresumeSession(sameturnId) or closed with the newabandonTurn(sessionId, turnId, reason). Parallel work goes to child sessions or separate sessions. Manual compaction between turns iscompactSession.A rate limit on a turn's first request pauses it. A retryable provider fault (a 429, an outage, a stalled stream) that survives the retries used to fail the turn when it hit before the turn's first checkpoint, which in practice meant its first request; the same fault after a checkpoint paused it. It now pauses there too, on a checkpoint of the turn where its loop began (
iteration: 0for a fresh turn, the restored count for a resumed one), andresumeSessionrestarts the turn from where that loop began. Everything the turn did since then is discarded: every iteration it ran before the fault, completed ones included (a partialmax_tokensanswer and the continuation prompt after it, a structured-output re-prompt), is not in the resumed request, and none of those iterations' guards is counted. So after such a fault the session holds a paused turn, and the nextquery()orrunAgentin it throwsTurnInProgressErroruntil the turn is resumed or closed withabandonTurn: a caller that retried by starting a fresh turn resumes instead. The returnedTurnhasstopReason: 'paused'rather thanstatus: 'failed', the event isturn_pausedrather thanturn_failed, and a paused turn'slastProviderErrornow carries the classification (retry delay included) that only a failed turn carried before. A permanent fault still fails the turn.Event literals.
run_started,run_completed,run_failed,run_pausedandrun_resumingareturn_started,turn_completed,turn_failed,turn_pausedandturn_resuming.subsession_spawned,subsession_messagedandsubsession_idledarechild_session_spawned,child_session_messagedandchild_session_idled. Every event dropsrunId, carriessessionId, and carriesturnIdinside a turn. The other 54 literals are unchanged.isEphemeralEventis now exported at runtime.Other string literals.
HandoffLockRejectedReason(andHandoffLockRejected.details.reason)'active_run'is'active_turn'.ResidentConsumptionAttempt.receiptStatus'duplicate-run'is'duplicate-turn'. A caller that compares either value must match the new one.Delegation records. The parent turn appends
child_session_spawnedandchild_session_endedto its own log, reading them from the task scheduler's new optionalonChildSessionEvent(callback).LocalTaskSchedulerandDelegatingTaskSchedulerimplement it. A hostTaskSchedulerthat does not still delegates, but its parent log names no children, soSessionIndexcannot list them: implement it to keep them listable.Storage.
- —
RunStore,RunDiskStore,InMemoryRunStoreandRunStoreConfigare removed, withwriteRunMeta,writeMessages,readMessages,writeReport,addToIndex,listRunsandlistChildren. UseSessionLog(DiskSessionLog,InMemorySessionLog): metadata comes from records, messages fromfoldSessionMessages, and listing fromSessionIndex(openSessionIndex,SqliteSessionIndex, orScanSessionIndexwherenode:sqliteis unavailable).readRunEventsInandreadRunMessagesInarereadSessionLogandfoldSessionMessages.RunMessageSnapshotis removed. - —
CheckpointStore,DiskCheckpointStoreandInMemoryCheckpointStoreareSessionCheckpointStore,DiskSessionCheckpointStoreandInMemorySessionCheckpointStore, keyed byCheckpointScope { tenantId, projectId, sessionId, turnId }.prune(scope, keepLast)is required; it was the optionalpruneCheckpoints. A checkpoint document holds no messages and is refused on restore when it no longer matches its log. A child session's checkpoints are in its own directory,<parent-session-dir>/subagents/<child-id>/checkpoints/; aDiskSessionCheckpointStoreyou build for a child takes that place as itssessionoption (DiskSessionLog.locator, which a log opened from its file path reads from that path, and which names the right place only for a file in that project's layout).IterationCheckpointand the kindrun-checkpointare replaced by theCheckpointdocument (kind: 'checkpoint'); an old one is refused by name. - —
TokenBudget,TokenBudgetStore,DiskTokenBudgetStore,InMemoryTokenBudgetStore,openTokenBudgetand their snapshot types areSessionTokenBudget,SessionTokenBudgetStore,DiskSessionTokenBudgetStore,InMemorySessionTokenBudgetStore,openSessionTokenBudgetandSessionTokenBudget*Snapshot. The ledger is keyed by(rootSessionId, rootTurnId), so each root turn has its own limit and a limit changed between turns no longer throwsToken budget root limit mismatch.TokenBudgetBindingisTurnBudgetBinding. A version 1 snapshot is refused. - —
SessionStore.appendMessage,replaceMessages,loadMessagesandloadSessionMessagesare removed: messages are written only by the turn recorder and read withfoldSessionMessages. A customSessionStoredrops those methods.SqliteSessionStoreandSqliteSessionStoreConfigare removed;SessionIndexreplaces them. - —Tasks live under
<session-id>/tasks/<task-id>.json, one store per session:DiskTaskStoreConfigis{ paths, session, tenantId?, logger? }, and a store opened on another session's tasks throwsTaskSessionMismatchError. Each task records the turn that created it;selectTaskContext(tasks, { turnId, turnStartedAt })gives the tasks a turn sees (every open task, plus those it closed). The task tools take aTaskToolScopeof{ sessionId, turnId, turnStartedAt }. - —
MessageFeedbackStoreis keyed by(sessionId, messageId)under<session-id>/feedback/, and feedback on a message the session log does not hold throwsUnknownMessageError. - —Custom log backends are checked with
defineSessionLogConformancefrom@namzu/sdk/testing.defineCheckpointStoreConformanceand its types are removed, and so areCHECKPOINT_STORE_CONTRACT_VERSION,CheckpointStoreCapabilitiesandDiskCheckpointStoreAttribution: a checkpoint store is keyed byCheckpointScope, andpruneis part of the interface rather than a capability to probe. - —
validateTokenBudgetBindingis removed with no replacement of its own. It checked the budget binding a checkpoint carried; a checkpoint's binding is now checked with the rest of the document byparseCheckpoint(thebudget.bindingofCheckpointSchema).
The emergency surface is removed.
EmergencySaveManager,EmergencySaveData,EmergencySaveConfig,EmergencySaveConfigSchema,EmergencySaveId,generateEmergencySaveId,asEmergencySaveId,EMERGENCY_DIR_NAME,EMERGENCY_EVENTS,EMERGENCY_SIGNALSandprojectEmergencyToCheckpointare gone. The log and the per-iteration checkpoints hold everything a dump held.prepareReplayState,PrepareReplayInputandPreparedReplayStateareprepareForkState,PrepareForkInputandPreparedForkState, andfromCheckpoint: 'emergency'is no longer accepted: the selector (CheckpointSelector) is a checkpoint id or'latest'. A replay forks a new session whosesession_started.forkedFromnames the source{ sessionId, turnId, checkpointId }.Layout. Everything lives under
NAMZU_HOME(default~/.namzu,resolveNamzuHome):projects/<slug>/<session-id>.jsonlplusprojects/<slug>/<session-id>/for child logs, spills, checkpoints, budgets, tasks, feedback, goals and the lease, andprojects/<slug>/{memory,residents, worktrees}/.SessionPaths,ensureProject,slugForCwdandtempRootcompute it.DefaultPathBuilderandPathBuilderare removed. Nothing is generated under the working directory: the default git worktree directory moves from<repoRoot>/.namzu/worktreestoprojects/<slug>/worktrees/. A project id is minted once per working directory intoprojects/<slug>/project.json. Old run trees,sessions.sqlite, checkpoints, ledgers and resident learning databases are not read.defaultStateRoot,NAMZU_STATE_DIRandprojectIdForDirectorywere on unreleasedmainand never shipped; they are not in this release. Neither is the per-run message history of that branch (RunHistory*:openRunHistory,loadRunHistory,resolveRunHistory,compactRunHistoryand the rest, with theirhistory/messages.<g>.jsonlfiles). Its idea, that each message is stored once, is the session log'smessagerecord: a message is appended once, when it ends, and a resume or a compaction refers to it by itsmessageIdinstead of copying it.Hooks. Hook events
run_start,run_endandrun_interruptareturn_start,turn_endandturn_interrupt; a config naming an old event is refused with a message naming the new one (RENAMED_PLUGIN_HOOK_EVENTS). Shell-hook stdinrun_idbecomesturn_idandparent_run_idbecomesparent_session_idplusparent_turn_id; envNAMZU_RUN_IDbecomesNAMZU_TURN_ID.session_idandNAMZU_SESSION_IDare always present;turn_idandNAMZU_TURN_IDare absent onsession_startandsession_end.PluginHookContext.runIdisturnId?.Memory. Consolidation tags are
session:<id>andturn:<id>(notrun:<id>), withmetadata.sessionIdandmetadata.turnId. The source value the session promoter stamps is'session-memory'(was'run-memory'), andMarkdownMemoryStoreleaves records with that source out of its index.Protocols and wire contracts.
- —
WireRun,WireRunStatus,toWireRunStatus,RunConfig,RunConfigSchema,RunIdSchema,CreateRunRequest,CreateRunSchema,CreateStatelessRunRequest,CreateStatelessRunSchema,RunUsage,RunStopReason,StreamEventandStreamEventTypeareWireTurn,WireTurnStatus,toWireTurnStatus,WireTurnConfig,TurnConfigSchema,TurnIdSchema,CreateTurnRequest,CreateTurnSchema,CreateEphemeralSessionRequest,CreateEphemeralSessionSchema,WireUsage,WireStopReason,SessionStreamEventandSessionStreamEventType. - —
WireTurnStatusgainsawaiting_input:awaiting_hitlandawaiting_hitl_resolutionmap to it (they mapped torunning), and A2A maps it toinput-required. - —SSE event names
run.*areturn.*, withsession_idandturn_id.mapRunToStreamEventismapSessionEventToStreamEvent. - —A2A:
contextIdnow names a session; it used to carry the project id. Any string is accepted and mapped to one session. A client that sent its project UUID ascontextIdgets one session for it, and its tasks queue there one at a time.Task.idis the turn id.runToA2ATask,mapRunToA2AEvent,runStatusToA2AState,RUN_STATUS_TO_A2A,CreateRunFromA2Aanda2aMessageToCreateRunaremapTurnToA2ATask,mapTurnToA2AEvent,turnStatusToA2AState,TURN_STATUS_TO_A2A,CreateTurnFromA2Aanda2aMessageToCreateTurn.TURN_STATUS_TO_A2Ais keyed byWireTurnStatus, so it has the newawaiting_inputkey. - —ACP: the default session id is a UUIDv7 instead of
acp_<n>, and a prompt while a turn is active is refused withINVALID_REQUEST. - —Telemetry:
NAMZU.RUN_ID,NAMZU.RUN_STATUSandNAMZU.RUN_PARENT_IDare removed; spans carryNAMZU.TURN_ID(namzu.turn.id),NAMZU.TURN_STATUSandNAMZU.SESSION_PARENT_ID, plusgen_ai.conversation.idset to the session id. The spannamzu.agent.run <name>isnamzu.agent.turn <name>and the histogramnamzu.run.durationisnamzu.turn.duration.
New
- —The session-log schema:
SessionRecord,SessionRecordSchema,parseSessionRecord, one*RecordSchemaper record type,TurnBoundSessionEventType,CheckpointwithparseCheckpoint, andrecordSha256,parseSessionLogLineandformatSessionLogLinefor one log line. - —
resolveA2AContext(withA2AContextResolution) andresolveExternalSession(withExternalSessionLookup,ExternalSessionResolutionandResolveExternalSessionOptions): how a host server resolves an A2AcontextId, or any protocol's own session id, to a session through the index. - —
PrepareStepContext.turnStartedAt: when the turn began. A resumed turn keeps the time of itsturn_startedrecord, whichever process wrote it. - —
ChildSessionStoragegains adiskkind ({ kind: 'disk', paths }). A parent on disk hands its layout to the children it delegates to, so their logs nest under it whether or not a layout was named. Code that switched onkindmust handle the new member. - —
resolveNamzuHomeandNamzuHomeError(moved from the CLI, same behaviour),SessionPaths,ensureProject,slugForCwd,hashedSlugForCwdandtempRoot. - —
fixtureId.turnandfixtureId.recordin@namzu/sdk/testing. - —
releaseHeldSessionLeases({ timeoutMs? })(withReleaseHeldSessionLeasesOptionsandReleaseHeldSessionLeasesResult), for a process about to exit: it releases every session lease the process holds, leaving a running turninterruptedso the next writer can abandon or resume it at once instead of waiting out the lease. After the first call every claim in the process is refused withSessionLeasesReleasedError. A host that stops on SIGTERM calls it from its signal handler; the CLI does.SessionLog.releasenow waits for the log's append in flight, so a release never lands under a half-written record.
What to do
- —On 43.x, drain or abandon every parked run.
- —Rename imports per the table above; the compiler finds every one.
- —Replace
runStore/pathBuilderwithsessionLog(orpaths), and read results fromTurnand the session log instead ofrun.jsonormessages.json. - —Re-implement a custom store against the session-log conformance suite, and
a custom checkpoint store with the required
prune. - —Update hook configs, dashboards and any A2A client that put a project id
in
contextId.
- —
- —
a84dc1c:
runAgentno longer mints a newprojectIdfor every call. When you pass noprojectId, it is the project ofworkingDirectory(defaultprocess.cwd()):ensureProjectmints one UUIDv7 the first time a directory is used, writes it to<NAMZU_HOME>/projects/<slug>/project.json, and every later call in that directory adopts it. Two processes that start in a new directory at the same moment get the same id. Every session started in one directory is filed under one Project, in oneprojects/<slug>/tree.What changes for you. Two
runAgentcalls in the same directory now return the sameidentity.projectId.sessionId,topicIdandtenantIdare still generated per call, so the calls stay separate conversations. To get the old behaviour, passprojectId: generateProjectId()on each call. An explicitprojectIdalways wins. - —
b064cea: A turn can make a file tool's path outside its roots, or a command outside its sandbox, a reviewed question instead of a refusal. Both are opt-in and default to the old refusal:
QueryParams.outsideRootAccess: 'review'andQueryParams.sandboxEscape: 'review'(also onReactiveAgentConfig).What can break.
AuditOutcome(and the session log'saudit.outcome) gains'approved', written for each approved crossing. A consumer that switches exhaustively over the outcome must handle it;replayAuditskips it. A session log written by this version with such a record does not parse in an older SDK.New, all optional.
ToolCallSummary.escalationnames what a call reaches (outsidePaths,sandboxEscape); an escalated call is always reviewed, and a gateallow, a remembered grant, a read-only declaration andaccept-editsdecline to approve it alone. A sandbox escape runs only when the decision lists its id in the newconfirmedEscalations(onapprove_toolsandmodify_tools);createReviewHandlerasks for it in every mode, and refuses it without aprompt(SANDBOX_ESCAPE_UNATTENDED_REFUSAL) unlessunattendedSandboxEscape: 'allow'. A path outside the roots is likewise asked about in every mode (autoand a rememberedapprove-allincluded) and refused without aprompt(OUTSIDE_ROOTS_UNATTENDED_REFUSAL);strictrefuses it, andplanasks about it when every call in the batch only reads. A refused crossing is an audit record with outcomerefused, adenyrule's refusal included. Tools declarepathArgumentandsandboxEscapeArgument; the executor hands an approved callToolContext.approvedPathsorsandboxEscapeApproved. The shipped file tools declarepathArgument: 'path';bashgains an optionaldangerously_disable_sandboxinput, refused withSANDBOX_ESCAPE_NOT_APPROVEDunless approved.pathOutsideRoots,toolRootsandOUTSIDE_ROOTS_GUIDANCEare exported.resolveWithinRealnow canonicalizes a root that does not exist yet (an approved path awriteis about to create) instead of refusing every path under it. A file tool's refusal of a path outside its roots now says how the boundary is widened instead of ending at "Tools may only reach". - —
3641102: Working memory leaves the system messages of every model request, a new
PromptPlacementvalue'context', and closing requests end with their closing directive.What breaks.
- —The working-memory slot is no longer a system message in a request. The
slot (pins, and a
workingMemoryProvider's block) keeps its place in the session's history, checkpoints and compaction: the last leading system message. Every request now takes it out of the system messages and sends it after the history as a user-role message with source{ type: 'runtime-context', kind: 'step-context' }. It is therefore missing from the system messages every provider receives, from therequest_envelopeevent'ssystemPrompt, and from the system messages apre_llm_callhook sees. There is no option to keep the old placement. The message's first line is the label every step-context message carries,Current step context (runtime-generated; not a new user request):, and the slot follows verbatim, so the content no longer starts withWORKING_MEMORY_HEADERandisWorkingMemoryMessage(content)answersfalsefor it. - —
PromptPlacementgains'context'. The union is also an output type:PromptContribution.placementandPromptContributionRegistry.list()can now hand back'context', so aswitchover it with an exhaustiveness check stops compiling. - —The closing directive is last. On the forced-final step at a resource
limit and on the request after an empty completion, the
[SYSTEM] ...message of kindlimit-finalizationnow comes after every request-only context message (working memory,contextcontributions, step context, derived work context) instead of before some of them.
What to do.
- —A host that finds the slot in a captured request by looking for a system
message starting with
WORKING_MEMORY_HEADERlooks instead for astep-contextmessage, drops its first line, and tests the rest withisWorkingMemoryMessage. A host that rewrites or strips the slot in apre_llm_callhook does the same. Code reading the session's history or a checkpoint needs no change. - —Code that switches over
PromptPlacementadds a'context'case. - —A test asserting the closing directive's position expects it last.
Why, and what is new. A driver may hoist every system message ahead of the conversation (Anthropic renders tools, then system, then messages), so a slot that changes whenever a pin does invalidated the cached conversation prefix and re-read the whole history at full price. After the history, a caching driver ends its breakpoint before it and a pin costs only its own tokens. A
PromptContributionregistered withplacement: 'context'is rendered before every model request, withiteration, exactly asturnis, and delivered the same way: one labelledstep-contextmessage after the history, never pushed onto it.turnis unchanged and keeps system authority; usecontextfor observations,turnfor text that must act as an instruction. - —The working-memory slot is no longer a system message in a request. The
slot (pins, and a
Minor Changes
- —
8805360:
BaseAgentConfig.pruneKeepLastsets checkpoint retention for a turn started throughReactiveAgentorSupervisorAgent, the same wayturnConfig.pruneKeepLastdoes for a rawquery()turn. Both agents build their turn config from a hand-listed literal, so a host that bounded its own turns could not bound a delegated child session's, and the child kept every checkpoint. Absent keeps every checkpoint, as before. - —
a14b013: Memories can be typed and kept as one Markdown file each.
- —
MarkdownMemoryStore({ directory })implementsMemoryStorewith one<name>.mdper memory (YAML frontmattername,description,type,status,createdAt,updatedAt,tags, then the body) and a generatedMEMORY.mdindex, one line per active memory of at most 150 characters. The index lists only what someone chose to keep: a record withmetadata.source: 'session-memory'(the session memory promoter) ormetadata.kind: 'consolidation'stays in the store and in search but is never listed, so a host carrying the index in its system prompt keeps the same prompt, and its prompt cache, when a turn writes one. Lines are orderedfeedbackandusermemories not saved by the model first, then the model's (save_memorystampsmetadata.source: 'agent-memory'), then the rest, by name within each, so the 200-line cap dropsprojectandreferencelines before an operator'sfeedbackoruserone. It keepsDiskMemoryStore's guarantees: the shared operation lock, atomic private (0600) writes, and refusal — naming the file — of anything it cannot read. It never writes what it would refuse: a NUL character in the body, title, summary, description or a tag, or a file over 256 KiB, is refused before the write with the newMemoryContentRejectedError(reason: 'too_large' | 'nul_byte'), whichsave_memoryandupdate_memoryreturn as a failed result. Two files left with one id by an interrupted rename — both stating their own, differentupdatedAt, or an older hand-written file with noidand noupdatedAtbeside the renamed file that carries its name-derived id — are resolved by the later one (the hand-written file dated by its modification time; when it is the newer file it is a new memory under a renamed one's old name, and the pair is refused), the older set aside as<name>.md.supersededat the next write; any other pair claiming one id, a hand copy that states itsidbut noupdatedAtincluded, is refused naming both files.readIndex({ maxLines })renders the index for a prompt, capped at 200 lines by default with a note pointing tosearch_memory, andreadIndex({ derived: true })lists only the derived records, newest first, for an operator's inspection;importRecordmoves records in from another store idempotently by id. While aDiskMemoryStoreindex.jsonsits in the same directory, every other operation is refused until its records are imported and the index moved aside. - —
MemoryIndexEntry,CreateMemoryParamsandUpdateMemoryParamsgain optionalname,descriptionandtype(user | feedback | project | reference). All three shipped stores persist them, and refuse anameanother record holds with the newMemoryNameConflictError, which names that record. Omitting the fields behaves exactly as before; an existing store, index or customMemoryStoreneeds no change. - —
save_memoryandupdate_memorytakename,typeanddescription; a taken name returns a failed tool result pointing atupdate_memory.read_memoryandupdate_memoryaccept a memory's name as well as its ID.search_memorylines show name, type and age. (read_memory's age footer changes its output and is described in its own changeset.) - —
createMemoryRecallStepaddsname,typeand, for a record older thanageNoticeAfterMs(default one day), anagefield, and then ends its block withMEMORY_VERIFY_NOTICE. A block of records updated within the day is unchanged apart from the new fields. - —New helpers:
renderMemoryIndex(records, { maxLines })(overMemoryRecords, since who wrote a record is in its metadata),memoryIndexLine,memoryLinkNames,describeMemoryAge,slugifyMemoryName(names derived from a title are at most 32 characters, so an index line keeps room for its description),isMemoryName,isMemoryType,assertMemoryType,MEMORY_TYPES,MEMORY_INDEX_MAX_LINES,MEMORY_INDEX_LINE_MAX_CHARS,MemoryContentRejectedErrorand itsMemoryContentRejectiontype. - —
DiskMemoryStore.getandgetRecordno longer return the file'sschemaVersionstamp as a field ofMemoryContent, which the type never declared.
Nothing is removed or renamed. A host that wrote a second record under the same title through
save_memorystill can: names are only enforced when a caller supplies one. - —
Patch Changes
- —9238347: A child session's log opened through a symlinked path is now recognised as
the log in its place in the layout. Before, when
NAMZU_HOME(or theSessionPathshome) was reached through a symlink and the log was opened by its real path, or the other way round, the two spellings compared unequal and the child's checkpoints were written a second time under its parent. Nothing to change on your side.
- —
Major Changes
- —
e83dfe5:
ModelInfo.inputPriceandModelInfo.outputPriceare now optional. Six drivers wrote0wherever the vendor listing carries no rate; they omit the field instead.A price of zero is not "I do not know" — it is a billing fact. It says the model is free, and it reaches every consumer as a quote: a menu prints
(free), a total reports the model as costing nothing, and an operator believes both. The drivers had no way to say anything else, because the type required a number.This is the defect
9d6c482cremoved fromcontextWindow, in the field where a wrong value is money rather than a compaction pass. The distinction was already the house rule one level down:resolveModelPricingreturnsundefinedfor a rate nobody has and{ inputCostPer1M: 0, outputCostPer1M: 0 }for a driver that genuinely bills nothing, and its own docblock says a caller that flattens the two reproduces the defect the module exists to remove.ModelInfocarried the flattened version to every consumer that never reached that module.What breaks. Code reading
model.inputPriceormodel.outputPriceas anumbermust handleundefined. That is the point — the value was already absent in fact, and the type was asserting otherwise. Treatundefinedas unknown rather than as free: a caller that renders a price should say "unknown" rather than$0.00, and only0means the model is free.What does not change. A driver that knows a rate of zero still writes
0.ollamaandlmstudiobill nothing by construction, and the Zen catalogue's free tier is priced from a source that names those models free. Values that were genuinely known are untouched.
Minor Changes
- —
9661d16: Stop an optional inference from ending the run that made it.
PreparationTextRequest.timeoutMsis deprecated and no longer bounds the request. An auxiliary provider request that ends without its final usage receipt leaves the run's shared token ledger unresolved, and an account with unresolved spend admits nothing further — so a deadline that fired in normal use did not bound the call, it stopped the run. Against a reasoning model whose auxiliary answer took 17 s, every turn after the first ended with "usage for a model request could not be confirmed" before the model was ever asked, so no tool call in the run could ever execute.What a host observes: a run that used to stop after its first turn now continues. A preparation or review inference is bounded by the provider's own request timeout and by the run's cancellation — the same two bounds every other model request in the run has — instead of by a 10 s deadline of its own; set
runConfig.timeoutMsto bound the whole turn. Stop still cancels an in-flight auxiliary call, and a cancelled call still leaves its unresolved receipt visible in/cost. PassingtimeoutMschanges nothing and is still validated; the field will be removed in a later major.
- —
Patch Changes
- —
175dab2: Nothing a consumer installs changes. These are devDependencies and toolchain pins: no runtime dependency, export, type or default moved, and the published surface is identical.
Four advisories, all dev-time, closed by moving the package that carried them rather than by patching around them:
- —
knip^6.4.1→^6.37.0, which carriessmol-toml≥ 1.8.0 — the version that fixesGHSA-7w5x-hrqm-74c2(a malformed-TOML denial of service). - —
tsx^4.19.0→^4.23.13, which depends onesbuild ~0.28.0, fixingGHSA-g7r4-m6w7-qqqr(arbitrary file read from the development server, and only on Windows) — and collapsing twoesbuildcopies in the tree into one. - —
js-yaml, twice, fixed by a workspace override rather than a parent upgrade, because both copies arrive deep inside the Changesets CLI's own tree:^3.15.2for the 3.x line and^4.3.2for the 4.x line, closingGHSA-2883-xcg3-v3hh(a merge-key CPU exhaustion). Neither override widens beyond what the parent already declared —3.15.2satisfies^3.13.1and4.3.2satisfies^4.1.0— so no package is forced outside its range.
- —
- —
8d5223b: Nothing a consumer installs or calls changes, and that is the whole of this release.
vitestmoves from^3.2.6to^4.1.11in thedevDependenciesof all nineteen packages that declared it, and@vitest/coverage-v8moves with it in@namzu/sdk. Every occurrence is a devDependency — checked, not assumed — sodependencies,peerDependencies, exports, types, defaults and the wire shape are untouched, and the published tarballs differ from the previous release only inpackage.json#devDependencies.The reason is a security fix with no 3.x backport.
GHSA-82fw-gwwq-j7x9("Path Traversal / Arbitrary File Read via@vitest/mockerRedirect Mock") coversvitestand@vitest/mockerfrom2.1.0up to4.1.11, so^3.2.6can only be resolved by leaving the 3.x line.4.1.11is the first patched release and is what the lockfile now resolves for both.What this costs anyone who works on the repository rather than with it: the upgrade was not a version bump. Vitest 4 changed test discovery, coverage configuration, mock construction and reporter output, and each of those broke something here that had to be migrated rather than worked around. Those fixes are all under
__tests__/,vitest.config.tsfiles andscripts/, none of which is published, which is why this is a patch and not a major.You do not need to do anything. If you pin
vitestyourself to run this project's own suites, note that the config files it ships are now written for>= 4.1.11and will not run under 3.x.
- —
@namzu/anthropic
5 releases
Minor Changes
- —3727e2b: Add
AnthropicProvider.listModelsStrict(signal?)for hosts that need the account's actual Models API rows and errors. Empty responses remain empty and authentication/network failures reject instead of returning bundled models. Pagination includes later account rows, preserves caller cancellation across pages, and rejects oversized catalogues rather than silently truncating them. Use it to present current account availability without treating an offline catalogue as proof of access. ExistinglistModelsfallback behavior is unchanged.
- —3727e2b: Add
Patch Changes
- —567ada8: Updated package examples and evaluation fixtures for the SDK's
ToolRegistryremoval. Browser, computer-use and LSP documentation now show toolsets; the evals usetoolsetswhen they call the kernel. Provider changes are test fixture migrations only, with no provider runtime API change. Consumers that pass tools to the SDK should usetoolsets: readonly Toolset[]and wrap their definitions withtoolset(source, definitions).
- —567ada8: Updated package examples and evaluation fixtures for the SDK's
Minor Changes
- —
28102e1: A forced tool choice that the vendor always rejects is now refused before it is sent. This covers
toolChoice: 'required'and a named function on Claude Opus 5.5, Claude Fable 5.1 and Claude Mythos 5.1. It also covers a forced choice on any model when the request carries manual extended thinking (thinking: { type: 'enabled' }after the driver resolves it). These requests used to go to the API and fail there with a 400. On the three models the message istool_choice: type "tool" and "any" are not supported for this model.What changes for you:
- —The request still fails, but earlier. You get a
ProviderRequestErrorwithkind: 'bad_request'andproviderCode: 'forced_tool_choice_unsupported'. It has no HTTPstatus, because nothing was sent. The message tells you what to use instead:toolChoice: 'auto'with the tool named in the prompt, orresponseFormatfor a fixed JSON shape. - —New export
acceptsForcedToolChoice(model, thinking?). It says whether a forced choice will be taken, using the same resolution the request uses. Use it to decide whether aprepareStepstage can force a call on the model it is about to run. - —No change for
'auto'or'none', or for forced choices on models that accept them (claude-opus-5,claude-fable-5,claude-sonnet-5and earlier, with adaptive thinking or none).
- —The request still fails, but earlier. You get a
Patch Changes
- —
28102e1:
claude-opus-5-5can no longer be asked to turn thinking off. The driver treated it likeclaude-opus-5, which acceptsthinking: { type: 'disabled' }at efforthighor below. Opus 5.5 cannot stop thinking and answersdisabledwith a 400 at every effort level.What changes for you:
- —A
thinking: { type: 'disabled' }intent is left out of Opus 5.5 requests. It used to be sent, and the request failed with"thinking.type.disabled" is not supported for this model. Now the model runs its default adaptive thinking, as it already did for the Fable and Mythos families. To spend less on thinking, lowereffort. Note that Opus 5.5 defaults to effortmediumwhen you leaveeffortunset. - —
disabledwith effortxhighormaxis no longer refused on Opus 5.5. It used to fail before sending witheffort "max" is not supported by model "claude-opus-5-5", although the model accepts all five levels. - —
resolveThinkingCapability('claude-opus-5-5')returnscanDisable: falseand all five levels in botheffortandeffortWhenDisabled.provider.effortLevelsFor('claude-opus-5-5', { type: 'disabled' })returns all five levels. - —A later Opus id (
claude-opus-5-6,claude-opus-6) resolves the same way.
claude-opus-5keeps acceptingdisabledathighor below. - —A
- —
Minor Changes
- —4375e72: A provider-hosted web search now says what it searched for.
StreamChunk.delta.hostedTool, and so thehosted_toolsession event and thehosted.toolSSE event, may carryquery(the search query),url(a page the provider opened instead of running a query) andresults(how many sources it reported). Codex fills them from the search call's action, Anthropic from the search block's streamed input and its result list, Google from the grounding metadata. Each field is optional and absent when the provider does not say; nothing that already readsid,nameandstatuschanges. A custom driver can start emitting them whenever it knows them.
- —4375e72: A provider-hosted web search now says what it searched for.
Patch Changes
- —a729b17: The error a stalled stream raises now says the turn lifecycle can emit
turn_failed, where it said the run lifecycle could emitrun_failed: the event it names was renamed in@namzu/sdk44. Only theProviderRequestErrordetail text changed; itskind(network) andproviderIdare the same. A caller that matched the old sentence should match onkindinstead.
- —a729b17: The error a stalled stream raises now says the turn lifecycle can emit
@namzu/bedrock
2 releases
Patch Changes
- —
49491b9: The drivers now report more accurately how a response ended. The runtime uses this to tell a tool call the output limit cut off from one the model wrote badly.
- —
@namzu/anthropic: a tool call whose JSON does not parse no longer fails the whole stream with "the provider stream returned malformed data". The block close and the finish reason now reach the runtime.model_context_window_exceededis reported aslength, andrefusalascontent_filter. Both used to read as a normalstop. - —
@namzu/bedrock:model_context_window_exceededis reported aslength, andguardrail_intervenedascontent_filter. A tool call opens with the id the driver keeps, so its arguments never arrive before an id. - —
@namzu/http: the OpenAI dialect mapsfinish_reasoninstead of passing the server's string through.function_callbecomestool_calls;max_tokensandmax_output_tokensbecomelength;model_length,context_length,context_length_exceededandmodel_context_window_exceededbecomelengthwithfinishDetail: 'context_window';eos,eos_token,end_turnandstop_sequencebecomestop.errorfails the stream with aProviderRequestError(kind: 'server'), as OpenRouter's does. A value it does not know reports no finish reason at all, which the runtime reads as a response that did not say how it ended, rather than as a normal finish. The Anthropic dialect gets the Anthropic mapping above and opens a tool call with the id its arguments carry. - —
@namzu/openrouter:finish_reasonis mapped the same way.error(the upstream model failed mid-generation) now fails the stream with aProviderRequestError(kind: 'server'). - —
@namzu/deepseek:insufficient_system_resourcenow fails the stream with aProviderRequestError(kind: 'server'). It used to read as a finished answer. - —
@namzu/openai: Codex'sresponse.incompleteis reported aslength, or ascontent_filterwhen that is the stated reason, with its usage. The stream used to end with no finish reason, so auto-continuation never ran. - —
@namzu/anthropic,@namzu/bedrockand@namzu/http's Anthropic dialect: amodel_context_window_exceededstop also carriesfinishDetail: 'context_window', so the runtime does not ask the model to continue a reply that filled the whole context window. - —
@namzu/anthropic,@namzu/googleand@namzu/openai(Codex): the list of sources a driver appends after a hosted search now carriescontentOrigin: 'driver'on its stream chunk. The text is unchanged. Without the mark, a tool call the output limit cut off before that list would be reported as malformed.
If you branch on
finishReason, expectlengthorcontent_filterwhere you sawstopfor these cases. - —
- —
2d4ff9a: Earlier entries in this package's CHANGELOG no longer name one particular application built on namzu, or quote a path from outside this repository. This changes only the CHANGELOG.md that ships in the tarball; no code or types change, and there is nothing to do to upgrade.
- —
Patch Changes
- —
3641102: Two fixes to the Converse request.
- —The message cache point ends the history. It was placed after the last
message. The runtime now appends request-only context there (the
working-memory slot,
contextprompt contributions, step context), which changes on every request, so every request wrote a cache entry no later request could read. The point now sits after the last block before the first request-only context message, as in the Anthropic driver. - —Consecutive same-role messages are merged. Converse requires the
conversation to alternate between
userandassistant, and the runtime sends tool results followed by request-only context — two user turns — on every step after a tool call. Their content blocks are now concatenated in order into one message.
No configuration changes.
- —The message cache point ends the history. It was placed after the last
message. The runtime now appends request-only context there (the
working-memory slot,
- —
@namzu/computer-use
4 releases
Minor Changes
- —072bb74: Windows computer use can capture pixels for one named window with
screenshot {window_id}and keep background clicks and drags, text and keys bound to that window. Windowtype_textandkeycan explicitly requestdelivery_mode: 'foreground'for browser text or modifier shortcuts that cannot run in the background; this may briefly bring the verified window forward. The pinned driver setswindowScroll: falseand refuses scoped pixel scrolling because its wheel input cannot prove which window receives it; a host that supports safe window scrolling can offer it. Capture identity and window geometry are checked before input; moved windows, old captures and restarted driver sessions require a new window screenshot. Hosts can offer window capture without display capture, in which casewindow_idis required and plain display screenshots remain unavailable. A failed window capture never broadens to a display capture. When both browser and computer-use tools are ready, the CLI routes a named existing browser window to computer use, leaving browser tools for the separate Namzu-managed profile.
- —072bb74: Windows computer use can capture pixels for one named window with
Major Changes
- —c8bb87a: The Windows cua-driver backend now revives an implicit session after its five-minute idle expiry and retries a call that the driver explicitly refused before dispatch. UI Automation snapshots now return adapter-owned refs instead of raw cua-driver element tokens. Callers passing raw tokens to
uiActmust use refs from the latestuiSnapshot; old refs cannot address a different control when the driver reuses a token after session revival or process restart. Calls whose outcomes are unknown are still not retried automatically.
- —c8bb87a: The Windows cua-driver backend now revives an implicit session after its five-minute idle expiry and retries a call that the driver explicitly refused before dispatch. UI Automation snapshots now return adapter-owned refs instead of raw cua-driver element tokens. Callers passing raw tokens to
Patch Changes
- —567ada8: Updated package examples and evaluation fixtures for the SDK's
ToolRegistryremoval. Browser, computer-use and LSP documentation now show toolsets; the evals usetoolsetswhen they call the kernel. Provider changes are test fixture migrations only, with no provider runtime API change. Consumers that pass tools to the SDK should usetoolsets: readonly Toolset[]and wrap their definitions withtoolset(source, definitions).
- —567ada8: Updated package examples and evaluation fixtures for the SDK's
Major Changes
- —
82769f1: On Windows and under WSL,
SubprocessComputerUseHostnow drives the desktop through cua-driver (MIT, github.com/trycua/cua) instead of startingpowershell.exefor every action, and every adapter now takes and returns physical pixels. Both are changes a caller can see; read on before upgrading.What changes by default on Windows / WSL. The first
initialize()downloads cua-driver 0.28.2 for the machine's architecture (a 27–29 MB archive from the project's GitHub releases), checks the SHA-256 of the archive and ofcua-driver.exeagainst values pinned in this package, and keeps only the executable in<NAMZU_HOME>/computer-use/cua-driver/0.28.2/(~/.namzuunlessNAMZU_HOMEis set). Onecua-driver.exeprocess then runs untilhost.dispose(); calldispose()when you are done. It runs with its telemetry and release check off and without environment variables whose names look like secrets. If it cannot be downloaded, verified or started, the host falls back to PowerShell and says why inhost.fallbackReason.To keep the old behaviour — no download, no long-lived process — set
NAMZU_CUA_DRIVER=offor passnew SubprocessComputerUseHost({ windows: { backend: 'powershell' } }). To use a cua-driver you installed yourself, setNAMZU_CUA_DRIVER=<path to cua-driver.exe>orwindows: { cuaDriverPath }.windows: { download: false }uses only a cached or configured build.Physical pixels everywhere. Points you pass to
execute()and sizes you get back are pixels of the captured bitmap, as the SDK's host contract now states. This changes results only on scaled displays:- —macOS Retina:
getDisplayGeometry()returns the capture's resolution (2880x1800, not 1440x900; 3360x2100 in a "Looks like 1680x1050" mode),cursor_positionis in pixels, and click/move/drag points are pixels; the adapter divides by the capture's pixels per point forcliclickand System Events, once. Before, a click aimed at the middle of a Retina screenshot landed near the bottom-right corner. Screenshots show the main display only, the one the input tools address. If you converted screenshot pixels to points yourself, stop. - —Windows above 100 % scaling: both backends are DPI aware, so the capture is the whole display (it was the top-left part) and points are physical (they were DPI-virtualised).
- —Every capture now carries
result.display(id, origin, size,scaleFactor).
Also new. With the cua-driver backend,
capabilities.windowsistrueandhost.listWindows()/host.focusWindow(id)work (focus restores a minimised window and reports what is actually in front);host.captureRegion()exists and throws where the adapter has no region capture.host.backendnames the backend in use. Measured on a 3440x1440 display: a click went from 0.68–0.83 s to 0.13 s, a screenshot from 0.40 s to 0.08–0.13 s.Smaller differences on the cua-driver backend.
capabilities.clipboardisfalse(no clipboard action was ever offered);win/superin a key chord press the Windows key (PowerShell's SendKeys pressed Ctrl); a single punctuation key such as/is typed as text so the keyboard layout cannot change it. Only the primary display is captured, as before. The PowerShell fallback now types text through Unicode key events instead of SendKeys, so+ ^ % ~ ( ) { }and non-Latin text arrive as written. - —macOS Retina:
Minor Changes
- —
82769f1:
computer_usecan read a window's controls and act on them by reference, on a host that declaresuiTree— which the Windows backend of@namzu/computer-usenow does.What changes for a caller of the tool
- —Two new actions when the host declares
uiTreeand implementsuiSnapshotanduiAct:ui_snapshot { window_id? }returns the window's accessibility tree as text, each control that can be acted on with a ref such ase12;ui_act { ref, action, value? }performsinvoke,set_value,toggle,select,expand,collapse,focusorscroll_into_viewon it and returns a screenshot afterwards.ui_actmay be in a batch;ui_snapshotmay not. Refs count up across snapshots and only the latest snapshot's are accepted, so a stale ref is refused rather than acting on another control.ActionInputgains both, so code that switches exhaustively overinput.typeneeds two more cases — that is why this is a major release for@namzu/sdk. - —
ui_snapshotis read-only;ui_actis destructive. - —
list_windowsnow returns its window lines inside an untrusted-content frame (<namzu-untrusted kind="desktop-windows">), since titles are whatever each application shows. The lines themselves are unchanged; code that parsed the whole output text needs to skip the frame's three header lines. - —
createComputerUseToolreturns aComputerUseTool, aToolDefinition<ActionInput>withdescribeUiRef(ref)(experimental), which names the control a ref points at for a review screen. - —
UiSnapshotgains optionaltitleandapp;UiElement.refis empty for an element nothing can be done with. Both types stay experimental.
@namzu/computer-useOn Windows and WSL with the cua-driver backend,
capabilities.uiTreeistrueandSubprocessComputerUseHostoffersuiSnapshot(windowId?)anduiAct(ref, action, value?), read from cua-driver's UI Automation walk. A control is invoked in the background, without moving the pointer or bringing its window to the front; a field without a settable value is typed into when it is empty. An action whose driver died mid-request comes back as not done with an unknown outcome, never retried. The PowerShell fallback and the other platforms do not declareuiTree, so nothing changes there. - —Two new actions when the host declares
- —
@namzu/http
1 release
Patch Changes
- —
49491b9: The drivers now report more accurately how a response ended. The runtime uses this to tell a tool call the output limit cut off from one the model wrote badly.
- —
@namzu/anthropic: a tool call whose JSON does not parse no longer fails the whole stream with "the provider stream returned malformed data". The block close and the finish reason now reach the runtime.model_context_window_exceededis reported aslength, andrefusalascontent_filter. Both used to read as a normalstop. - —
@namzu/bedrock:model_context_window_exceededis reported aslength, andguardrail_intervenedascontent_filter. A tool call opens with the id the driver keeps, so its arguments never arrive before an id. - —
@namzu/http: the OpenAI dialect mapsfinish_reasoninstead of passing the server's string through.function_callbecomestool_calls;max_tokensandmax_output_tokensbecomelength;model_length,context_length,context_length_exceededandmodel_context_window_exceededbecomelengthwithfinishDetail: 'context_window';eos,eos_token,end_turnandstop_sequencebecomestop.errorfails the stream with aProviderRequestError(kind: 'server'), as OpenRouter's does. A value it does not know reports no finish reason at all, which the runtime reads as a response that did not say how it ended, rather than as a normal finish. The Anthropic dialect gets the Anthropic mapping above and opens a tool call with the id its arguments carry. - —
@namzu/openrouter:finish_reasonis mapped the same way.error(the upstream model failed mid-generation) now fails the stream with aProviderRequestError(kind: 'server'). - —
@namzu/deepseek:insufficient_system_resourcenow fails the stream with aProviderRequestError(kind: 'server'). It used to read as a finished answer. - —
@namzu/openai: Codex'sresponse.incompleteis reported aslength, or ascontent_filterwhen that is the stated reason, with its usage. The stream used to end with no finish reason, so auto-continuation never ran. - —
@namzu/anthropic,@namzu/bedrockand@namzu/http's Anthropic dialect: amodel_context_window_exceededstop also carriesfinishDetail: 'context_window', so the runtime does not ask the model to continue a reply that filled the whole context window. - —
@namzu/anthropic,@namzu/googleand@namzu/openai(Codex): the list of sources a driver appends after a hosted search now carriescontentOrigin: 'driver'on its stream chunk. The text is unchanged. Without the mark, a tool call the output limit cut off before that list would be reported as malformed.
If you branch on
finishReason, expectlengthorcontent_filterwhere you sawstopfor these cases. - —
- —
2d4ff9a: Earlier entries in this package's CHANGELOG no longer name one particular application built on namzu, or quote a path from outside this repository. This changes only the CHANGELOG.md that ships in the tarball; no code or types change, and there is nothing to do to upgrade.
- —
@namzu/lmstudio
1 release
Patch Changes
- —
49491b9: A response
contextLengthReachedcut off, after it had produced content, now carriesfinishDetail: 'context_window'alongsidefinishReason: 'length'. It used to report a plain'length'finish, indistinguishable frommaxPredictedTokensReached, so the runtime auto-continued it — sending a reply that had just filled the model's context window straight back in, in a prompt now longer than the window it had just overflowed.maxPredictedTokensReachedis unchanged: still a plain'length'finish, since the output-token budget (not the conversation's length) is what ran out, and the runtime's ordinary auto-continue is right for it.contextLengthReachedwith no content is unaffected:chatStreamstill fails that turn with acontext_overflowerror before any finish reason is reported. - —
2d4ff9a: Earlier entries in this package's CHANGELOG no longer name one particular application built on namzu, or quote a path from outside this repository. This changes only the CHANGELOG.md that ships in the tarball; no code or types change, and there is nothing to do to upgrade.
- —
@namzu/ollama
1 release
Patch Changes
- —2d4ff9a: Earlier entries in this package's CHANGELOG no longer name one particular application built on namzu, or quote a path from outside this repository. This changes only the CHANGELOG.md that ships in the tarball; no code or types change, and there is nothing to do to upgrade.
@namzu/openai
4 releases
Patch Changes
- —023224a: A failed OpenAI or Google request now reaches the host with the provider's own type, code, parameter and request ID, as an Anthropic failure already did, so Details read "Status: 403 · Provider said: “…” · Type: invalidrequest_error · Code: model_not_found" with "Request ID: …" on its own line. The error's
detailfor a vendor SDK error whose message is only a sentence is now a JSON body of those fields ({"error":{"message","type","code","param"},"request_id"}) instead of the bare sentence; readproviderCodeor theDetailsof a host rather than matching the sentence text. The Google driver now reads its refusal body and request-ID header. The vendor project line in Details reads "Project: proj…".
- —023224a: A failed OpenAI or Google request now reaches the host with the provider's own type, code, parameter and request ID, as an Anthropic failure already did, so Details read "Status: 403 · Provider said: “…” · Type: invalidrequest_error · Code: model_not_found" with "Request ID: …" on its own line. The error's
Patch Changes
- —567ada8: Updated package examples and evaluation fixtures for the SDK's
ToolRegistryremoval. Browser, computer-use and LSP documentation now show toolsets; the evals usetoolsetswhen they call the kernel. Provider changes are test fixture migrations only, with no provider runtime API change. Consumers that pass tools to the SDK should usetoolsets: readonly Toolset[]and wrap their definitions withtoolset(source, definitions).
- —567ada8: Updated package examples and evaluation fixtures for the SDK's
Patch Changes
- —82769f1: The Codex (Responses) driver sends images returned by tools with
detail: "high"instead of"auto". Withautothe backend could pick the 512-pixellowview of a screenshot, so a model answered in coordinates of an image the tool never produced. Images the user attaches still go withauto. No change is needed on your side.
- —82769f1: The Codex (Responses) driver sends images returned by tools with
Minor Changes
- —4375e72: A provider-hosted web search now says what it searched for.
StreamChunk.delta.hostedTool, and so thehosted_toolsession event and thehosted.toolSSE event, may carryquery(the search query),url(a page the provider opened instead of running a query) andresults(how many sources it reported). Codex fills them from the search call's action, Anthropic from the search block's streamed input and its result list, Google from the grounding metadata. Each field is optional and absent when the provider does not say; nothing that already readsid,nameandstatuschanges. A custom driver can start emitting them whenever it knows them.
- —4375e72: A provider-hosted web search now says what it searched for.
@namzu/openrouter
2 releases
Patch Changes
- —567ada8: Updated package examples and evaluation fixtures for the SDK's
ToolRegistryremoval. Browser, computer-use and LSP documentation now show toolsets; the evals usetoolsetswhen they call the kernel. Provider changes are test fixture migrations only, with no provider runtime API change. Consumers that pass tools to the SDK should usetoolsets: readonly Toolset[]and wrap their definitions withtoolset(source, definitions).
- —567ada8: Updated package examples and evaluation fixtures for the SDK's
Patch Changes
- —
3641102: Prompt caching now places explicit breakpoints instead of sending a top-level
cache_control: { type: 'auto' }, a value OpenRouter does not define (its top-level field takes{ type: 'ephemeral' }and puts the breakpoint on the last cacheable block — with request-only context at the tail, the part the next request replaces).For
anthropic/,google/geminiandqwen/models, the models OpenRouter documents as needing explicit breakpoints, a cache request marks the last static system message and the last non-system message before request-only context, each inside a content part (a string becomes one text part). A system message directly before the context is the step's guidance, which changes from step to step, so it is passed over rather than marked. Other models, which OpenRouter documents as caching automatically, are sent no marker and keep their plain string content.
- —
ag-ui
3 releases
Patch Changes
- —6194bda: Keep questions and frontend tool calls answerable when live tool-start, progress or question-park events arrive around the interrupt boundary. The adapter now expires a wait on evidence that its own call or turn ended, rather than on any unread native event. Buffered events retain their order on resume, and existing client-answer deadlines and cancellation remain in effect.
Major Changes
- —567ada8: The AG-UI adapter now reads tools and review metadata from the query's
toolsets, preserving review exemptions and per-tool timeouts after theToolRegistryremoval. Hosts must returnQueryParams.toolsetsfromcreateQueryinstead of passing aToolRegistryastools. Wrap definitions withtoolset(source, definitions)and require@namzu/sdk >=48.0.0; use@namzu/ag-ui2.x with SDK 45.1–47. MCP resource tools likewise rely on their owning toolset for source trust instead of a removed per-tool provenance field.
Patch Changes
- —567ada8: Exercise repeated AG-UI runs with one stable native session owner and verify that a host resolving the same session under another tenant is refused before its history reaches a model. This updates the integration checks for the SDK's stricter session attribution; the adapter API is unchanged.
- —567ada8: The AG-UI adapter now reads tools and review metadata from the query's
Major Changes
- —
3e7a97b: Requires
@namzu/sdk >=44.0.0(was>=36.0.0), where the kernel's run became a turn inside a session. Earlier@namzu/ag-uiversions break against SDK 44.What changes on the wire. The AG-UI ids themselves do not:
RUN_*events still echo the client'sthreadIdandrunIdverbatim. Only the Namzu-owned names change.- —Error codes on
RUN_ERROR:NAMZU_RUN_ERROR,NAMZU_RUN_CANCELEDandNAMZU_RUN_PAUSEDareNAMZU_TURN_ERROR,NAMZU_TURN_CANCELEDandNAMZU_TURN_PAUSED. - —New
NAMZU_TURN_IN_PROGRESS: a second run on a thread whose Namzu session already has an active turn is answered withRUN_ERRORcarrying this code instead of starting a parallel one. - —The pause custom event
namzu.run.pausedisnamzu.turn.paused.
What changes for a host. A thread maps to a Namzu session and each AG-UI run to a new turn in it. The client's
runIdis recorded as the turn'sorigin.externalTurnId, never used as a Namzu id.MESSAGES_SNAPSHOTis the session's folded transcript, so it shows the answer after any guardrail or review rewrite, never the raw text.Renamed exports.
AGUIRunContext,AGUIRunOptions,AGUIRunUIandAGUIRunUIOptionsareAGUITurnContext,AGUITurnOptions,AGUITurnUIandAGUITurnUIOptions, with the same shapes. The AG-UI protocol's own names (RunAgentInput,onRunFailedand the other callbacks) are unchanged.What to do. Upgrade
@namzu/sdkand@namzu/ag-uitogether, rename imports of the four types above, and rename any client code that matched the old error codes or custom event name. - —Error codes on
- —
browser
4 releases
Patch Changes
- —
d8782dd: The interactive terminal and
namzu execstart about 150 ms to 250 ms faster. They no longer import@namzu/sandbox,@namzu/browser,@namzu/telemetryand the browser engine just to learn that those packages are installed; the launch-time capability line now reads the packages' manifests, and@namzu/browserloadsplaywright-coreon the first browser launch instead of when it is imported.One consequence: a capability package that is installed but fails to load is no longer reported at launch (the
namzu.capability.brokenrow). It surfaces where the package is first used, andnamzu doctorstill imports each package and reports it.probeOptionalPackagetakes an optional{ load: false }for the manifest-only answer, andprobeCapabilitiesuses it by default; nothing a caller of either passed before changes.
- —
Major Changes
- —d3e35af: Visible password or one-time-code fields on an ordinary public page no longer trigger a whole-page sign-in or second-factor handoff. Component galleries and settings pages can now be read and navigated. Actions targeting credential controls, submission of forms containing them, and all untargeted keys are refused; unrelated controls remain usable. Page-created dialogs can be dismissed but not accepted by the agent. Screenshots mask input and textarea pixels that Playwright can locate, including child frames, but closed shadow roots and text drawn elsewhere may remain visible; snapshots still hide detected credential values. A recognized sign-in or second-factor address, HTTP authentication challenge, CAPTCHA, bot wall, or a bare HTTP 401 page with a credential field still triggers a handoff. If your application relied on a field alone to hand off a sign-in page at an unusual address, add that address to
signInAddresses.
- —d3e35af: Visible password or one-time-code fields on an ordinary public page no longer trigger a whole-page sign-in or second-factor handoff. Component galleries and settings pages can now be read and navigated. Actions targeting credential controls, submission of forms containing them, and all untargeted keys are refused; unrelated controls remain usable. Page-created dialogs can be dismissed but not accepted by the agent. Screenshots mask input and textarea pixels that Playwright can locate, including child frames, but closed shadow roots and text drawn elsewhere may remain visible; snapshots still hide detected credential values. A recognized sign-in or second-factor address, HTTP authentication challenge, CAPTCHA, bot wall, or a bare HTTP 401 page with a credential field still triggers a handoff. If your application relied on a field alone to hand off a sign-in page at an unusual address, add that address to
Major Changes
- —c8bb87a: The exported
classifyHumanRequiredno longer returnshttp-authfor a bare HTTP 401 or 407. Callers that relied on that result must supply the matchingwwwAuthenticateorproxyAuthenticatesignal, or handleundefinedas access denied. The browser host now fails bare 401/407 page loads instead of asking a person to sign in. Pages with separately detected sign-in, CAPTCHA or bot-check signals still request a human handoff. Failed navigation attempts, history commands with no destination and same-document URL changes retain the current document's access result, including its challenge headers. A back-forward cache restore reuses the restored document's access result.
- —c8bb87a: The exported
Patch Changes
- —567ada8: Updated package examples and evaluation fixtures for the SDK's
ToolRegistryremoval. Browser, computer-use and LSP documentation now show toolsets; the evals usetoolsetswhen they call the kernel. Provider changes are test fixture migrations only, with no provider runtime API change. Consumers that pass tools to the SDK should usetoolsets: readonly Toolset[]and wrap their definitions withtoolset(source, definitions).
- —567ada8: Updated package examples and evaluation fixtures for the SDK's
cli
21 releases
Patch Changes
- —f86a456: A Pal's conversation now uses the Pal's current model. Before, the model the Pal had when its conversation opened was kept for good, so changing the Pal's model afterwards (or setting it after the page filled in a default) never reached the conversation. Nothing to change on your side; a model picked by hand in the Pal's composer is still kept for that conversation.
- —Updated dependencies [d328c1b]
- —@namzu/zen@4.0.2
Minor Changes
- —
fa24b15: Scroll back through the conversation without leaving Namzu. Press PgUp at the message box and a view of the whole conversation opens above it, one page up; PgUp and PgDn page, the arrow keys move a row, Home goes to the start. While you are reading older text, one quiet line above the message box says
↓ Back to bottom · esc(shorter on a narrow terminal), and starts withNew activity ·if the reply keeps arriving while you are scrolled up. Press End or Esc, or scroll down to the last page, and the view closes with the session exactly as you left it. Your terminal's own scrollback is untouched and nothing changes unless you press PgUp.This release also brings existing users the
@namzu/sdk50.1.0 and@namzu/zen4.0.1 tool-schema fix. The CLI did not publish a new version with it, sonamzu upgradehad nothing to install; this version does. If a free Zen model refused a request that included the edit tool, upgrading fixes it and you need to do nothing else.
- —
Major Changes
- —
c82b951: Change the local Pal provider's default foreground ownership from strict command lifetime to computer lifetime. Rebuild the shipped local-computer image from this release to use the new default. To retain the previous behavior and continue using an existing strict image, set
normalExitPolicy: 'strict'when callingcreateLocalVirtualComputerProvider. A successful foreground launcher can leave Blender or another application open without retiring the entire computer; the application remains owned by that guest allocation until the computer stops. An authenticated execution-reservation acknowledgment prevents an upgraded host from silently admitting this policy against an older image.Generic workers and registered background jobs retain strict process ownership. Failed launchers and timed-out or cancelled commands cannot transfer live descendants; if termination cannot be confirmed, the worker retires the allocation. A completed launcher does not claim its surviving application has terminated.
CLI Pal computers use the same new default. Set
NAMZU_PAL_COMPUTER_NORMAL_EXIT_POLICY=strictbefore starting the CLI or desktop host to retain their previous command lifetime and use an older strict image. - —
0f5aa96: Ready-computer Pal prompts now default to general application work guidance: understand references and acceptance criteria, make reversible changes, observe and correct the actual result, and validate saved files and requested exports before delivery. SDK hosts that need the previous prompt behavior can pass
workGuidance: 'basic'tobuildPalSystemPrompt. This guidance shapes model behavior; it does not certify semantic correctness.The SDK
readtool now refuses real PNG, JPEG and WebP bytes as text. Mount the optionalview_image/createViewImageToolfor saved-image inspection, or use a format-aware binary reader when pixels are not the desired result.getBuiltinTools()retains its previous tool roster. Saved artifact images provide no GUI screenshot authority.The CLI and desktop Pal runtime now explicitly mount
view_imageandimport_reference_images. Image import is a reviewed file-write operation using only the current operator input and admitted guest. Original image bytes and manifests survive durable review; current plan, pause and control guards remain in force. Ordinary chat and unavailable-computer sessions do not acquire guest tool access.The local Pal sandbox adds authenticated, acknowledged bounded byte reads. Rebuild the local Pal image from this release to inspect saved images; an older worker fails explicitly before file contents are requested. Existing whole-file requests and generic worker defaults are unchanged.
The Pal image also includes the standard
fileutility for format checks. This reports file type; it does not establish application or visual quality. - —
4367edc: Manual Pal conversations now default to automatic approval for tools in their owned virtual computer across the terminal, ACP and desktop. Previously a review callback or the interactive composer selected Ask first, requiring repeated approvals. To retain that behavior, explicitly choose Ask first, use
/permissions promptin the terminal, or sendpermissionMode: 'prompt'through ACP. Existing explicit desktop selections are preserved.Ordinary conversations retain their existing defaults. Explicit Plan, strict and review selections, configured deny rules, host escape refusal, ownership, pause and operator-control fences remain effective. This grants no authority for host files, other Pals or ungranted communication routes.
- —
57c99b2: Scheduled script jobs can opt out of a project folder with
workspace: none(--workspace nonein the CLI). Namzu creates a private working directory only after operator confirmation, and a script never loads that directory as a project configuration.Interactive schedule proposals now bind noteworthy results to the exact source conversation. The CLI stores them as durable host notices and displays them on resume without adding a fake model turn or changing the model's conversation history. An optional JSON script report lets a polling script distinguish a quiet check from a change and carry a bounded scheduler-owned state value between runs.
The CLI default for newly confirmed interactive proposals changes from detached results to source-conversation delivery. An unavailable source keeps its result pending and holds later runs until delivery succeeds. Existing jobs retain their previous behavior. To keep a new job detached, review
namzu schedule edit <job> --delivery none; the operator must confirm any exact pending results being waived. New workspace, source-delivery and report fields use job format v3, which older CLIs refuse rather than silently ignore. Upgrade the scheduler service together with the CLI. - —
c590142: New scheduled agent phases now default to unlimited tokens (
tokenBudget: 0) instead of 500,000 tokens. A positive request or configured limit still applies, and existing saved jobs retain their confirmed values. Setlimits.tokenBudgetto500000or pass--token-budget 500000to retain the old default for new jobs. To remove an existing job's limit, runnamzu schedule edit <name> --token-budget 0and confirm the edit. Iteration and timeout defaults remain unchanged.The SDK schedule tool accepts zero for an unlimited agent token allowance. Unlimited previews omit the numeric daily ceiling and identify the absence of a token limit; usage accounting continues to measure actual tokens.
- —
12b5af9: Session task snapshots now reach model requests as user-role runtime context instead of system instructions, including checkpoint resumes and compaction. Agent-maintained subjects and descriptions are planning data. Move any host policy formerly encoded in task descriptions to trusted instructions; task storage, tools and durable conversation records are unchanged.
- —
f7f48f9: Add the explicit
cmdShellDialect and change the native Windows host-shell metadata default fromshtocmd. The physical command shell remains Node's platform shell (normally CMD); no PowerShell or WSL switch occurs. Update exhaustive dialect handling to includecmdand pass the actual value to authorization helpers.CMD lines are conservatively opaque until a matching parser exists. Command-specific allow rules and skill patterns no longer pre-approve native CMD commands. The existing unknown-program escalation still requires exact-call review, even when the gate has a whole-tool allowance. Use that review or explicitly select a POSIX interpreter rather than incorrectly labelling CMD as
sh. Sandboxed Linux guests retainsh.SDK query prompts disclose the active command tools' host or guest execution dialect, including minimal and cached prompts. Explicit Windows
bash.exeandsh.exeoverrides retain their matching POSIX dialects.Native Windows CLI model shell calls inherit the exact-call review requirement. The scheduled-run floor refuses CMD commands rather than checking them with POSIX quoting; confirmed script jobs still accept only explicit bash/sh interpreters. Use an installed POSIX interpreter where appropriate or run reviewed interactive CMD calls.
Minor Changes
- —
4314f90: A tool call a person declined at review is now recorded as declined by them, with what they said, so a host that reopens the conversation can show it. Nothing you already send or receive changes shape, and no code needs to change to upgrade.
New and optional:
ToolReviewAnswer'sreject,AcpPermissionOutcome'srejectand thereject_toolsdecision takedeclined?: { note?: string }; an ACP client'srejectanswer takes the same field; and a genericToolCallViewresult carriesdeclined?: { note?: string }. A host that setsdeclinedon its reject gets, on that call'stool_completed, a view whoselabelis the call's target (path or command) and whosenoteis the person's words, cut at 4,000 characters. A host that does not set it, a call the authorization gate refused even when the person rejects the rest of its batch, and every other refusal by policy (strict or plan mode, no one to ask, the authorization gate, a repeated failure), is recorded exactly as before: an error result with no presentation. The text the model reads is unchanged.ToolExecutor.executeBatchtakes an optional fifth argument for the same purpose.The CLI's ACP server passes the field through, so a client such as Namzu Desktop that reports "the person said no" now shows "Declined" after a reload instead of "details unavailable". The terminal UI does not report it yet, so a call declined there still reloads as before.
- —
7f5ac50: Expose explicit paused-turn recovery through optional
AcpAgentGateway.retryandACPServer.retrySession. Hosts retain the existing ordered update, review, cancellation and single active execution owner without inventing a new prompt.The desktop CLI host advertises scoped retry-status and retry methods. It resumes only the exact verified checkpoint of a classified retryable provider pause with resolved original accounting. Human decision holds and uncertain provider usage remain blocked, including unlimited turns; this feature never resets a ledger or abandons an active turn. Same-process retries preserve captured approval/effort and the recorded model. After reconnection, default Retry is unavailable when the original approval settings cannot be verified; a deliberate host retry must explicitly select a permission mode in ordinary conversations. Pal recovery also pins the authenticated original computer generation and environment identity before sending, and rechecks them at resumed provider/tool entries. A cold, originally offline, replaced or unavailable Pal computer lifetime cannot be substituted with the currently ready computer. Pal ownership and current computer-control guards remain required.
- —
53aed2a: Add authenticated Pal channel ingress with captured host connections, a fresh actor per event, immutable native conversation targets, and shared durable inbox delivery. Recorded reply and action routing verifies the original owned conversation receipt and current authority; it does not send remote messages automatically. Hosts must provide a trusted event verifier and explicit receive, wake, reply and action policy. The CLI includes a private local HMAC fixture adapter and a bridge to exact native parked tool-review actions; other channel actions remain unsupported.
- —
8cf64b3: Offer reasoning effort for the second external engine. Its model rows now carry the effort levels the engine reports (none for a model without support), so the Desktop and terminal effort controls appear. A turn that asks for a different level restarts the engine on the same session between turns with
--effort; a level the model does not offer is refused before anything is sent. Nothing to change for callers who select no effort: the engine keeps its own default. - —
24cb6b4: Compose installed Codex CLI app-server and Claude Code persistent stream-json engines behind the ordinary desktop conversation host, with their native model catalogues, exact session/profile/workspace binding, streamed message replacement, one-request native tool approval and confirmed interruption/shutdown. Keep these routes separate from Namzu's borrowed Codex and Anthropic provider credentials. The desktop selects the engine before first dispatch and manages conversations as peer tabs with their own drafts, engine bindings and selected models.
The native app-server adapter offers its declared review policies, including explicitly confirmed Full access; the initial stream-json adapter offers supervised and plan modes. Both omit unsupported controls and explicitly deny unsupported typed questions. Codex models advertise only their discovered effort options; the initial Claude adapter offers no effort control, and neither route accepts attachments yet. Native metadata is not proof of successful account authentication. Retain uncertain sends and approvals for reconciliation rather than replaying them; the initial engine has no authoritative history-query adapter.
- —
afc5bf5: The desktop host adds two methods,
namzu/conversations/archived(list a project's archived conversations) andnamzu/conversations/unarchive(restore one bysessionId). Both require folder trust, and restore only admits an owned, archived, non-Pal conversation. Existing methods are unchanged; nothing to do on upgrade. - —
cea5487:
namzu computer setuptakes--resume-command <command>, the command Windows runs once at the next sign-in when setup needs a restart. Nothing changes if you do not pass it. - —
4d8f504: Add
namzu computer status,namzu computer setupandnamzu computer stop-idlefor Pal computers on Windows.statussays in one sentence whether this PC is ready;setupshows what it will install, the download size, the time, and whether Windows will ask permission or a restart may follow, asksContinue? [y/N], then runs;stop-idleshuts down the idle Linux machine.--jsonprints versioned JSON lines for Namzu Desktop (setup --jsonneeds--plan <id>as the consent).setupwithout a terminal and without--yesor--planexits with the new code 75 after printing the plan.--bundle <dir>orNAMZU_PAL_COMPUTER_BUNDLEnames the folder with the bundled image; withNAMZU_PAL_COMPUTER_ENGINE=wslcand noNAMZU_PAL_COMPUTER_IMAGE, the Pal runtime uses the manifest'simageRef. On Windows a Pal computer that is not set up now says "Run namzu computer setup to set this up.", and the model is told that the computer is not set up. Nothing changes for existing commands; the agent runtime still never installs anything. - —
3c2bda7:
namzu acp --desktopnow hosts pseudo-terminals for the desktop application: tennamzu/terminal/*methods (status, create, list, attach, detach, write, resize, ack, kill, close) and two notifications (namzu/terminal/data,namzu/terminal/exit). A terminal keeps a bounded replay ring and a headless screen, one view holds the keyboard at a time, output is paused when the viewer falls behind, and killing a terminal ends its whole process tree, including programs that detached themselves withnohuporsetsid(the host lists a terminal's descendants before the polite stop and kills the survivors), and a terminal's folder must be inside the project after links are resolved. Nothing changes fornamzu,execor any other command, and a host that predates this answers-32601for the new methods.node-pty@1.1.0is a new optional dependency and@xterm/headless(now pinned to 6.0.0, previously a development dependency) and@xterm/addon-serializeare new runtime dependencies. node-pty ships Windows and macOS binaries and compiles on Linux at install time; if that fails the install still succeeds, the rest of the CLI is unaffected, andnamzu/terminal/statusanswersavailable: falsewith the reason. With pnpm 10, a workspace that depends on@namzu/cliand wants terminals on Linux must allow node-pty's build script (onlyBuiltDependencies). - —
3c2bda7: The interactive
namzu(andnamzu resume) now accepts--provider <id>,--model <id>,--effort <level>and--permission-mode <prompt|accept-edits|auto|strict|plan>, written before any command. They choose how that one launch starts and are never saved:preferences.jsonis not touched, so the next plainnamzustarts where you last left it, and/model,/effortand/permissionsstill change the session as before.--providerreplaces the provider chain with that provider alone,--modelalone re-models the saved primary (it needs a saved provider, otherwise the provider list opens and says so),--permission-modewins over--yolo, and an--effortthe model does not offer is reported and the provider default stays. An unknown provider opens the provider list with the reason instead of starting unusable. Before a subcommand other thanresumethese flags are refused with exit 64 rather than ignored;execanddrainkeep their own--provider,--model,--effortand--permission-modeafter the command name. - —
fc86c05:
NAMZU_PAL_COMPUTER_ENGINEacceptswslc(Windows' own container CLI) besidesdockerandpodman, andNAMZU_PAL_WSLC_BINARYpoints at a specificwslc.exe. Unset keeps Docker, exactly as before. Any other value still stops the Pal computer with an error, which now reads "must be docker, podman or wslc". - —
846557b:
namzu acpnow attaches a file change preview to the permission request of aneditorwritecall: the path as the tool resolves it, the file's current body (nullfor a new file) and the body after the call, computed with the SDK's own apply code. Files over 1 MiB, binary files, paths outside the turn's directory and calls the tool would refuse get no preview. Clients that do not read the newpreviewfield are unaffected. - —
fae17a3: The desktop host now answers
namzu/project/untrust, and the trust store exportsuntrustDir(dir). They remove only the entry that names the exact folder from~/.namzu/trust.jsonand report an ancestor entry that still covers it asstillTrustedBy, so a caller can say the folder remains trusted instead of claiming it was removed. Nothing existing changes:trustDir,isTrustedandnamzu/project/trustbehave as before, and a host that predates the method is simply not asked.trustDiranduntrustDirnow writetrust.jsonto a uniquely named temporary file and rename it, so a concurrent reader never sees a partial file. - —
888e37b: A paused provider turn that has no token limit can now be retried in place even when earlier requests have unknown usage (a 502 is the usual cause).
namzu/sessions/retry-statusthen answers{ retry, unknownUsage: <count> }instead of a notice, and the retry is a new request; the unknown usage stays recorded. A turn with a finite limit still answers the notice and refuses the retry, so an ACP or Desktop client that treated every unresolved-usage pause as "cannot retry" will now see a retry target for unlimited turns.Desktop connections gain
namzu/sessions/abandon-paused(optionalturnId): it closes the paused provider turn without repeating it (turn_failed, codeabandoned, its unknown usage kept on the ledger) so the same conversation takes the next message. It refuses a turn that waits for a person's decision or did not stop on a provider error. Hosts that do not advertise the method keep today's behaviour. - —
0b247f4:
namzu acp --desktop:namzu/sessions/retry-statusadds two optional fields,closable: true(the paused turn stopped on a provider failure and waits for no decision, sonamzu/sessions/abandon-pausedmay close it before a new message) andretry.original({provider, model}that stopped).namzu/sessions/retrynow runs the paused turn on the model the conversation has selected, not on the model that failed, and no longer refuses when the provider differs; the approval settings the turn began under are kept across a model change. A client that ignores the new fields behaves as before, except that a retry after choosing another model now runs on that model instead of being refused. - —
1fa60b9: An API key for a provider can now be saved once and kept. Namzu stores it in
api-keys.jsonin the Namzu home (created private to you and verified private after writing, like the other credential files; Google keeps its own file), reads it after the environment, and shows it assaved API key · this device. A key from an environment variable still wins, so nothing you exported is replaced. Until now a key pasted in the terminal picker was held for that session only; that path is unchanged, but a key saved some other way is now found by every Namzu screen.namzu acp --desktopadvertises five new methods for the desktop app's Settings:namzu/providers/connections(every provider and how it is connected, never a key),save_key,remove_key,test(a cheap authenticated check, not a model turn) andrefresh.DetectionSourcegains astored-api-keykind and the provider status rows may carryanonymous: truefor the keyless free tier.Removing the last saved key deletes
api-keys.json; delete it by hand to forget keys without the app. - —
cdb4aec: File history now outlives the session. Closing the CLI used to delete the snapshots
/restorerolls back to; it no longer does. Each conversation keepsfile-history/turns/<turn id>.jsonand content-addressedfile-history/blobs/<sha256>(both the file before and after everyeditandwrite) until the conversation is deleted, 30 days after its last edit, or the history passes 512 MiB, whichever comes first, oldest turn first. Expectfile-history/to take disk space you did not have before; deleting a conversation removes it, and no flag or config key tunes the 30 days or 512 MiB yet. After a crash the next open settles any half-recorded edit,/restorelists the turns of the conversation that is open (including after a restart or/resume), and edits from a resumed stream land in their own turn instead of a stray one./restore Nno longer overwrites blindly: it undoes turn N and every later turn newest first, checking each file against what the turn left, and a file you (or a shell command) changed since is left as it is and listed with the reason instead of being replaced; if a later turn's change to a file could not be undone, the older turn's change to it is left too. The undone turns are no longer deleted: their manifests and bodies stay, marked undone, they leave the/restorelist, and a turn with files left alone stays listed as partly undone so/restore Nrun again finishes the rest. The report also names files no history covers and says when a turn ran shell commands. There is no force option in the TUI: to restore a file it kept, put it back to what the model left (or restore it by hand) and run/restore Nagain. This is a minor, not a major: where nothing changed since the turn the result is the same as before; the only difference is that a destructive overwrite of your own later edits no longer happens. A symlink is resolved before it is recorded, a sandboxed edit is never snapshotted from the host path of the same name, a restore writes through a temporary file and rename and keeps permission bits, and a failed edit leaves no entry. Nothing exported, no flag, config key or wire shape changes, so this is a minor.namzu acp --desktopalso gains three extension methods,namzu/turns/undo-status,namzu/turns/undo-previewandnamzu/turns/undo, for a client that undoes one reply's file changes (see docs/cli/turn-undo.md); a client that does not call them sees no change. - —
33f9109: Add optional desktop ACP live-input extensions for active ordinary Namzu conversations. A bounded, scope-owned mailbox delivers operator text at safe query boundaries and releases delegation waits without cancelling the child. Hosts can reconcile pending or delivered input IDs after cancellation or a lost acknowledgement; core overlapping prompt refusal, existing next-turn queues and native-engine routing remain unchanged.
- —
56e846c: Expose the selected model's exact reasoning effort choices and per-message tool permissions to the desktop operator. Read installed plugin metadata without starting a model session or executing plugin modules; plugin changes remain scoped to an existing idle conversation.
Add scoped desktop-host methods for reasoning settings and plugin inventory. Explicit plugin enable/disable choices remain local to the conversation and are restored when changing its model; they do not update startup configuration.
Agent sessions expose optional image/document attachment support declarations from their exact primary provider route. ACP rejects new attachments before a model request when that route explicitly cannot receive them; undeclared support retains the existing runtime policy.
- —
a5e0038: Codex model names in the Desktop model list now read like the Codex app ("GPT-5.6 Sol" instead of "GPT-5.6-Sol"), and each engine's model list marks that engine's own recommended default with an optional
default: trueflag on the row. Namzu provider lists no longer say "Namzu default" in a row's note; the flagged row replaces it. Model ids are unchanged, so saved selections keep working; clients that read the model list can ignore the new flag. - —
e94e149:
namzu acp --desktopgains four methods for the Desktop conversation header:namzu/conversations/rename({ sessionId, title }returns{ title }; an empty title restores the title derived from the first message),namzu/conversations/fork({ sessionId }returns{ id, title }; refused while a reply is running or when there is nothing to copy),namzu/conversations/markdown({ sessionId }returns{ markdown, truncated }, cut at 4 MiB of UTF-8) andnamzu/project/git(returns{ branch, subject }, ornullwhen the folder is untrusted, not a repository, git is missing or git takes over 3 seconds). All are new; existing methods and clients are unaffected. Each conversation method needs a trusted folder and a conversation this project owns, the same as archive. - —
9ef6506: Expose live tool progress and actual turn failure messages through optional fields on existing ACP update variants. Add explicit optional
namzu/*ACP host extensions and advertise their method names. Gateway history loading now receives the requested workspace as an optional second argument; existing one-argument gateways remain compatible.The CLI now loads durable, project/tenant-scoped ACP conversations and refuses archived writers.
namzu acp --desktopenables operator methods for folder trust, scoped conversation/history, safe provider metadata and background jobs. Ordinary ACP connections retain the core method set. The private desktop preview uses this existing runtime and log rather than a second conversation store. - —
3ff96b4: Add the
namzu/providers/modelsdesktop ACP extension for reading a configured provider's model catalogue on demand. Results contain model IDs, labels, optional notes and an explicit fallback notice when listing fails or is unsupported. The extension reuses the terminal picker's access filter, keeps permitted default and session-specific choices, and never returns provider credential envelopes or raw driver error text. Existing exact model selection remains available. - —
5e2fb3a: Desktop hosts expose six additive Pal communication methods for known peer consent, inbox delivery metadata, and activity subscription listing, creation and disabling. Reads and writes use the authenticated Pal workspace and captured application home, exact revisions, and original source conversations. The first communication request pins the physical home for that host connection; replacing it requires reconnecting even when copied IDs and revisions match. Incoming permission is read-only; reverse and wake grants remain separate explicit actions.
These management methods do not invoke a model, start a computer, dispatch messages or run a CLI subprocess. Snapshots omit message bodies, host paths and cursors; source labels include explicit user names, while derived prompt titles remain
New conversation. Unreadable records reject with redacted errors rather than erasing known rows. Partial subscription setup remains disabled and unknown writes are not automatically retried. - —
56d88dc: Add the desktop host's read-only
namzu/tasks/listextension and opt into negotiatednamzu/tasks/updatenotifications. Restore the existing planning list from its durable session store before new work, preserving failed outcomes and deletions. Scope reads to the authorized project, session, tenant and Pal claim; exclude task descriptions, metadata and filesystem paths. Older clients continue using the existing ACP update union. - —
56e846c: Add optional inline user attachments to ACP prompt requests. Hosts opt into delivery with
supportsPromptAttachments: true; initialization then advertisespromptAttachments: true. Clients can check this capability before sending bytes, and gateways receive the validated attachment payload alongside the prompt. Stored attachment references are refused across this boundary; inline payloads are limited to eight attachments and 3 MiB of decoded bytes per message.The CLI opts in and preserves image/document attachments in the actual user message and settled conversation history. Existing plain text requests keep their behavior. The private desktop admits native file picks or dropped/pasted bytes as bounded images and UTF-8 text, keeps draft and queue ownership, and refuses image submission to an older CLI rather than silently dropping it.
New image/document inputs are refused before send when the selected live provider explicitly declares that it cannot receive them. Desktop files remain available for retry with a suitable model. Attachment/settings-only drafts survive a connection rebuild, and queue editing restores captured options together with its text and files. Pre-turn CLI errors retain their actual diagnostic rather than becoming a generic failed turn.
ACP also accepts optional
AcpPromptOptionswith a reasoning effort and tool review mode. Hosts opt in withsupportsPromptOptions: true, advertised aspromptOptions: true; unsupported or malformed explicit settings are rejected before a turn starts. The CLI validates reasoning effort against the actual selected runtime's supported menu and applies the captured review mode to that turn. - —
0f3df20: Add an optional versioned
workdisplay snapshot to ordinary desktop conversation history. It restores retained message and turn identities, actual saved action outcomes and bounded recorded public tool views from the same strict journal read. Existing text messages and limits are unchanged. Legacy missing details stay explicit; reopened history grants no execution, approval or retry authority. - —
2057fd6: Surface provider-hosted web search and fetch activity through existing ACP tool updates, using qualified IDs and only provider-reported state and bounded public captions. These display receipts do not grant local execution or approval.
Desktop conversation history adds optional validated journal message IDs and timestamps for messages and work, and restores saved hosted search receipts. Missing timing and unfinished operations remain explicit; reopening history does not invent a current clock or a successful outcome. Existing message text and execution authority are retained.
- —
dc306f3: The Desktop host (
namzu acp --desktop) gains two read-only ACP methods,namzu/project/changesandnamzu/project/diff, so the Desktop Changes view can review uncommitted work.changeslists the working tree against HEAD (status, added and removed lines, renames, binary and untracked files, at most 2,000 files) and answersnullfor an untrusted folder or a folder that is not a repository;diffreturns both sides of one changed file, confined to the project folder and capped at 2 MiB. Nothing to change on upgrade: an older Desktop never calls them, and a newer Desktop talking to an older CLI simply hides the view. - —
6194bda: Send an operator message directly from a selected child transcript while preserving the parent's draft. Running children accept bounded input at the next valid request boundary; finished shared-workspace children retained by this process can receive a new task in the same conversation with current permissions, credentials and budget. Saved-only and isolated-workspace continuation is refused. The parent receives host-attributed assignment notices and explicitly framed child results; a durable observation journal preserves unacknowledged reports across restart without restoring execution authority.
Only a parent response ending with
end_turnacknowledges its captured observation snapshot. Timeouts and other unsuccessful stops keep notices pending, including stops before a model request; observations arriving after that snapshot are not acknowledged accidentally. - —
e447a47: The desktop model catalogue (
namzu/providers/models) now carries an optionalcurrent: trueon a row when the execution engine itself says the model is current rather than an older release: the Claude Code engine's alias rows (a model id that names no version) and the Codex engine's default row. A client that ignores the field is unaffected, and rows without it read as before. Nothing needs changing to keep the old behaviour. - —
d063837: Opening an external engine from the desktop host (
namzu acp --desktop) starts the engine's process once instead of two or three times. The Codex app-server that model discovery starts is kept (closed after two idle minutes, or when the connection closes) and the first conversation opened in that folder takes it over instead of spawning again; re-reading the model list reuses a running server too. A model list read in the last ten minutes is answered from memory, so opening the picker no longer spawns the engine each time, and the last good list of each installed engine build is kept inengine-models.jsoninNAMZU_HOME(ids, labels and effort levels only), so a later launch names the default model without waiting for the engine. A new build of the engine (its executable's path, size or modification time changes) drops its old list at once.What changes for an ACP client:
namzu/harnesses/selectno longer waits for the engine's model list, so an engine that cannot list models (signed out, no models) is accepted there and the error comes fromnamzu/providers/statusornamzu/providers/modelsinstead; the status row of an external engine carries an optionalidentity(a short hash naming the installed build) for clients that cache the list; the harness view and the model listing carry optionaltimings(spawn, initialize and model-list milliseconds, no paths or account details) that a client may record. Nothing changes for callers that ignore the new fields. To keep the old always-fresh read, there is no flag: a list older than ten minutes is read again on the nextnamzu/providers/models. - —
d063837:
namzu acp --desktopgains the host methodnamzu/harnesses/release, which takes anengine(codex-cliorclaude-code) and ends the idle engine servers the connection keeps for it, answering{ released: true }. It never touches a conversation that is running; it only closes the Codex app-server that model discovery parked, so a client can replace the engine's program on disk (a running executable cannot be overwritten on Windows) before it updates it. The Desktop uses it before running an engine's update command. A client that never calls it sees no change, and a host that predates the method answers-32601, which the Desktop treats as "nothing to release". - —
b6ea5ca: Models work on first open with nothing to set up. With nothing saved, Namzu Desktop and the terminal app now start on what they find, in this order: a sign-in Namzu already reuses (Claude Code, Codex), an API key in the environment, a local server (Ollama on port 11434, LM Studio on port 1234; each probe waits at most 300 ms and they run together), then the keyless Zen free models. They say what they picked in plain words ("Using your Claude sign-in, found on this computer.") and save nothing, so a key or sign-in added later wins on the next open.
/modeland the model picker change it.What changes for you: in the terminal app a first run with an API key or a local server in the environment no longer stops at the provider picker; it starts on that provider. The picker still opens when nothing is available. In Desktop the keyless Zen free models now count as usable, as they already did in the terminal app, so a new person's message box is ready and Send works at once; the "Add an API key or sign in to start" card now appears only when nothing at all is available. To keep the old terminal behaviour of choosing yourself, save a choice once with
/model.When Zen declines a free request that carried no key, both apps now say "Zen didn’t accept the free model right now. Try again in a while, or choose another provider. A Zen API key also works." and keep the technical detail under Details (Desktop) or
--debug(terminal). The genericprovider.permissionexplanation in@namzu/sdkno longer says the key authenticated: a 403 only says the request was refused. - —
79a7354: Add
--message <text>to the interactivenamzu: the session sends the text once, as a plain prompt (a leading/,!or#is not a command), when its composer is ready, with the provider, model, effort and mode chosen by the other launch flags. It is session-only, shown as the first user message, and not sent again on resume or reload. Before a subcommand it is refused with exit 64, like--model. Nothing changes for callers that do not pass it. Pass--message=<text>when the text may start with a dash. - —
7473449: Interactive terminals in the same project can discover and message one another with
/peers,list_sessionsandsend_session_message. Idle terminals can start a turn for peer context; busy terminals receive it at the next safe request boundary. Peer input remains separate from operator instructions and cannot grant approval. Different permission modes are refused. Use/peers offto refuse new mail and pause delivery in this terminal.The mailbox is bounded and process-local; queued is an acceptance receipt, not proof of model delivery. Conversation switches cannot redirect pending mail. Child-task activity now labels accepted messages as queued, and SDK manager documentation describes the inbound callback that query-backed agents actually consume. The SDK peer envelope refers to the host's reply tool rather than prescribing a CLI child-task tool.
- —
6f46da3: Add durable Pal addresses, owned conversation routes, incoming message storage, captured senders, explicit directed consent and executor-correlated messaging tools. Acceptance is separate from verified session-log delivery and recipient execution; unresolved claims are retained for recovery without repeating effects.
CLI and desktop Pal sessions use the same SDK messaging tools and durable query input. Add
pal grant/revoke/inbox/dispatch: wake consent is explicit and finite dispatch uses preapproved tools. No external transport, automatic listener or Pal Team membership is installed by this feature. - —
9dad339:
namzu/sessions/abandon-pausedaccepts an optionalwithdrawMessagesboolean. Absent ortrueit behaves as before: the closed turn's messages leave the history the next request carries. Sendfalsewhen the person goes on with a different message and the closed turn's message should stay in what the provider is shown. A reloaded Desktop conversation no longer shows the stopped partial reply of a turn whose message was withdrawn. Nothing breaks for a caller that sends only the conversation. - —
84a9472: Add optional provider-owned Pal computer takeover through
PalComputerControl,PalComputerInputand runtime control methods. Hosts can transfer an idle guest to human input using its exact generation, return it without waking the Pal, and observe current control authority. Providers without the optional port retain normal Pal execution and explicitly refuse takeover.The runtime serializes control operations, refuses new Pal admissions during operator control, and requires each subsequent admission to obtain its own fresh screenshot before GUI input. CLI desktop ACP exposes take-over, return and bounded input methods; optional screen generation pinning rejects stale captures.
- —
53aed2a: Add durable subscriptions for closed Pal activity metadata from an exact owned original conversation. Observation, disclosure, recipient receipt and idle wake have independent current permission checks. Custom publication authorizers must also implement the final accept phase requiring current observation, disclosure and receipt together. Accepted facts enter the shared Pal inbox before cursor progress; retries preserve identity and verified observation trails suppress feedback.
The CLI adds finite pal subscribe, subscription, activity and unsubscribe commands. Publication performs no inference or guest startup; explicit dispatch still requires current wake/tool authorization. Original turn intake and exact recorded delivery receipts are required for causality, and unknown evidence or exhausted journal read budgets reject without advancing progress.
- —
53aed2a: Resume a Pal's real parked tool review in its original session and local guest computer. Explicit review holds record durable checkpoints, preserve the conversation's pinned profile and original turn limits on resume, and hold later batches for their own decision. Existing live terminal permission prompts keep their behavior when no hold is requested.
Trusted hosts can authenticate one-batch review actions against the exact original journal request and current consent. Durable operation and decision reservations prevent automatic replay, and acknowledgement requires an actual recorded decision resolution. Unconfirmed attempts retain their ownership for reconciliation rather than executing again.
The required trusted host
currentPermissionModecallback keeps current plan mode stricter than a restored approval and later rule-allowed batches. Channel action payloads cannot change the mode or substitute the captured host ports. For this action,autoandaccept-editsare limited toprompton later review requests, so a one-batch answer never grants automatic later approval. Independent operator rules and ordinary CLI automatic modes retain their existing behavior. - —
5d91ac1: The desktop host's
namzu/pals/communication/inboxanswer now carriesreceivedAt(epoch milliseconds, when the Pal's inbox accepted the input) on every row, and, for a message the owner sent from their own conversation (sourceKind: operator-conversation), the messagetext(up to 4,000 characters) so the owner can read back what they approved. Messages from Pals and channels still carry no text, and the existing fields are unchanged. Nothing breaks: a client that ignores unknown fields keeps working, and a strict one that compared whole rows must allow the two new keys. - —
7f5ac50: Allow owned Pal conversations to use explicitly installed application MCP servers inside their original virtual computer. Install a reviewed guest bridge and a version 1
/home/namzu/.config/namzu/mcp.jsonmanifest with absolute guest paths, an explicit tool allowlist andoutcomeProtocol: "namzu-v1"; host MCP preferences and credentials remain excluded.Retain rich MCP output and provenance, current permissions, writer and computer lifetime checks. Do not retry or take over after a bridge loses application completion evidence. The bundled pinned Blender bridge reports uncertain socket operations before upstream turns them into successful text replies, preserving the computer recovery fence.
- —
ce52b90: Add optional host-only
PalComputerScreenStream,PalEnvironmentLease.screenStreamandPalRuntime.computerScreenStream(palId, generation)for observing an exact current Pal computer over read-only RFB. Existing providers remain compatible; providers without the optional capability explicitly refuse live observation. Keep allocation authorization private in the embedding host and enforce read-only observation server-side; the stream does not grant operator input authority.The CLI exposes the owning desktop ACP stream method with geometry and generation rechecks, and reuses initialized registry roots to avoid repeated Windows ACL subprocesses while retaining fresh Pal definitions and directory identity checks. The local computer image adds x11vnc and authenticated binary WebSocket transport. Rebuild the installed image explicitly and restart the owning computer to enable live observation; older images retain screenshot/input behavior and advertise no stream capability. Persistent workspace and browser profile volumes survive.
The guest desktop includes a themed wallpaper, a real dock, Files, terminal and browser launchers, plus a local browser home page. Normal browser-window closure keeps the desktop alive; owned profile shutdown retains browser flush ordering.
- —
815433f: Add
DiskPalStore.delete(id, expectedRevision)and the desktop host'snamzu/pals/deletemetadata endpoint. Deletion uses a terminal immutable revision, hides the current Pal, and refuses future updates and execution admission while preserving historical profiles, journals, host files, and guest data volumes. Hosts must stop active work and confirm computer cleanup before deleting its identity. Existing customPalStoreimplementations require no new method. Readers from older releases do not understand terminal deleted profiles and should not be used to read a registry after deletion has been published.Add the desktop host's
namzu/conversations/archiveendpoint. It performs a scoped, writer-gated soft removal while retaining the durable conversation and files. Successful retries returnarchived: true; a physically absent strict journal returnsarchived: false, missing: true, never claiming that an absent conversation was archived. Hosts may discard only their own known unsent local projection from this missing receipt. Existing terminal archive commands are unchanged.Release a connection's exact idle native-engine writer before desktop archival, reserving the conversation until the operation settles. Active or unresolved work is refused and a failed native close retains cleanup authority for retry. The runtime reservation method is optional for existing embeddings.
Keep a Pal's approved stored control-path spelling when Windows native path resolution changes only its case, after verifying the same physical directory. Physical project identity remains canonical, while existing Pal profile revisions and journal ownership paths remain unchanged. Different directory objects and non-case aliases are refused.
- —
6f46da3: Add optional
PalAppearancepreferences to saved Pal definitions and create/update inputs. Character and color selections persist in immutable profile revisions; existing records retain their absent appearance and hosts may choose their own display default.The CLI accepts
--appearance <character>/<color>onpal createandpal update, and the desktop ACP create/update extensions carry the same validated preference. Supported characters arepixel,sprout, andspark; supported colors aregreen,blue,amber,violet, androse. - —
f7f48f9: Add persistent Pal identities, immutable profile revisions and an SDK runtime that requires an explicitly provided virtual computer before execution. Hosts can retain one warm computer per Pal, serialize active conversations, enforce current pause state and retry failed computer cleanup without losing ownership.
Add
namzu pal list/create/show/update/pause/resume/chatand desktop host Pal methods. Pal conversations pin a saved profile and bind exactly one Pal in their session log. File, shell and computer tools run in the Pal guest, with no host folder, host browser or host plugin fallback. The local computer image and engine must be set up explicitly before chat can execute. - —
6943add: The Desktop can now start a Pal for a message waiting in its inbox, on the person's click. After an approved
send_pal_messageto a Pal that is not running, the sender's transcript asks "Start name now?" and the Pal's own page offers the same (or Resume for a paused Pal). Start runs the same finite dispatch asnamzu pal dispatchin the Pal's own host, in the background: the sender's turn is never held, the Pal reads the message in its own conversation under its own permission rules, and its approvals stay in its own tab. A Pal that is already running is only reported, repeated clicks start once, and a missing Pal computer is explained in plain words with Retry instead of starting.The CLI's desktop host gains two methods,
namzu/pals/inbox/statusandnamzu/pals/inbox/start; an older runtime simply shows no question. Nothing about sending changes: a message still only reaches the inbox, andsend_pal_messagestill never starts a Pal itself. If you embed the CLI's authorization, an owner-conversation wake now also accepts a click-evidence object (operatorWake: { evidence }) besidestrue; with neither, a wake is still refused. - —
6ff961f: The Namzu engine's ordinary conversation (terminal and Desktop) can now give a Pal work. It mounts
list_palsandsend_pal_messagein every such session, and its system prompt names the owner's Pals, separate from sub-agents, only while some exist; a session with no Pals leaves no Pal files.send_pal_messagealways asks the person (in every permission mode and even after "allow all";strictand a session with nobody to ask refuse it) and returns "Sent to name's inbox" as durable acceptance, never delivery or an answer. A paused or removed Pal is refused. The message lands in that Pal's inbox as untrusted context from the owner's conversation; it does not start a stopped Pal, whichnamzu pal dispatch(typed by the owner) still does.namzu pal inboxand the Desktop Pal communication view list such a message asoperator-conversation. External engines and a Pal's own sessions are unchanged.If you keep an exact list of the ordinary session's tool names (a plugin allowlist test, a snapshot), it now includes
list_palsandsend_pal_message; withhold them withwithheldTools(withholdingsend_pal_messagealso drops the prompt block).Desktop: the approval card reads "Message to name" with the whole message and a note that it goes to the inbox only, and the action row reads "Messaged name" with the hover text "Sent to inbox", including after a reload.
- —
53aed2a: Read and dispatch Pal messages, authorized activity observations and authenticated channel messages through one SDK ledger.
namzu pal inboxpreserves existing peer row fields and adds explicit source metadata for observation/channel rows without exposing message bodies. Pal sessions and finite dispatch check source-specific current consent; channel execution denies unless a trusted host adapter is explicitly injected. - —
083e4f9: The terminal app now reports problems in plain words. A failed or paused reply is one sentence that names the provider (for example "OpenAI did not accept your API key. Check it in /setup, or pick another provider with /model."), with no bracketed error ids, checkpoint ids, raw JSON or config-key names; a paused reply continues with Enter. The exit hint prints
namzu resume <id>instead of the node path, and an unexpected crash prints one line and saves the stack to~/.namzu/logs/crash-<time>.log. If you scraped the old terminal text ([provider.auth],Checkpoint preserved:,Fatal:), read the structured output ofnamzu exec --jsoninstead, which is unchanged; setNAMZU_DEBUG=1or pass the new global--debugflag to see the technical detail on screen.
Patch Changes
- —
b8852a1:
abandonTurntakes a new option,withdrawMessages. Pass it when you give the turn's prompt back to a person to send again (a message box restored after "continue without this reply"): the abandoned turn then contributes no message to the next turn's context, so the provider receives the re-sent prompt once instead of twice. The records stay in the log; the closingturn_failedcarriesfailure.details.messagesWithdrawn: true, which the fold honours. Nothing changes unless you pass it: an abandoned turn's messages stay in the context by default.SessionLog.abandonTurnaccepts the same option as a fourth argument; a customSessionLogthat wraps another should forward it.The CLI's
namzu/sessions/abandon-pausedpasses it, which fixes Desktop's Continue without this reply sending the person's message to the provider twice. - —
efb7b06: Extend the closed
AcpSessionUpdateoutput union withagent_thoughtreasoning boundaries andagent_messagecompleted messages. Exhaustive consumers must add handlers for both kinds before upgrading. Existing variants and required fields remain available. Reasoning boundaries contain no private replay payload; readable reasoning still arrives throughagent_thought_chunk.Message chunks carry optional actual message/turn identity and public commentary/final-answer metadata. Completed messages provide selected settled content and ordered public text parts.
turn_endedcan provide the authoritative result and its actual answer-message identity; consumers must replace the preview when a result is present, including an empty result that clears blocked output.Keep the coarse
AcpStopReasonunion and add optional exactreasonto terminal updates and prompt responses. Current runtime guardrails/refusals map torefused, resource limits tomax_turns, and parked segments retainreason: 'paused'under the older coarse cancellation label. Legacy aliases remain accepted. Prompt preparation cancellation returns an explicit cancellation reason even if no runtime event was produced. Streamed updates are sent in admission order and finish before the prompt response. Tool completion retains optional runtime-measured duration.Permission questions wait for already-admitted updates and fail closed when that context was not delivered. Update errors are caught immediately, retain the first failure and release the prompt slot after flushing; failed question sends settle their pending wait. Delivery failure does not undo runtime work already recorded.
Update the CLI session-isolation regression expectations to include the exact cancellation and completion reasons; the CLI runtime is unchanged.
- —
a32c679: Mark unfinished Claude messages closed by native failure or interruption as cancelled instead of completed replies. Preserve partial text, the failed turn's actual status and error, and messages the engine already completed.
- —
642104d: Preserve observed Claude tool calls and results when the engine emits thinking, text and tool blocks under one native message ID. Complete streamed messages at the native message boundary instead of the first content block, retain authoritative inputs and interrupted partial text, and ignore duplicate or older-operation receipts. Public wire shapes and permission admission are unchanged.
- —
6ff961f: Removing a sidebar conversation in the desktop app no longer fails with "This conversation does not belong to this project." when its journal is gone or was saved under a regenerated identity; the host now names the identity change and leaves the journal untouched.
- —
dbc466a: The terminal app's header now names the product: "Namzu v35.0.0 · by Cogitave".
- —
2852da1: The CLI now ships as a code-split bundle, so a launch reads a handful of files instead of about 1,700 modules:
namzu --versiondrops from about 49 ms to 36 ms, the terminal's first frame from about 555 ms to 265 ms, andnamzu execreaches the model about 120 to 210 ms sooner. Thenamzubin name and path (dist/bin.js), every command, option and output stay the same, and no code needs to change to upgrade.What to know if you inspect the install:
dist/holds a few dozen bundled files and chunks plus the type declarations, not one file per source module, so a deep import of@namzu/cli/dist/...(never inexports) no longer resolves.quickjs-emscripten-coreand@jitl/quickjs-wasmfile-release-syncare now direct dependencies of the CLI, because the bundled SDK finds them by path; they are the same versions the SDK already required.@namzu/browser,@namzu/sandbox,@namzu/telemetry,@namzu/computer-useandnode-ptyare still loaded from your install, not bundled. - —
42ef5e4:
namzu computerand the Pal computer hint read better on Windows. The "Run namzu computer setup" hint is chosen from the sandbox's setup code, not by matching its message, and its wording no longer mentions wslc. A failed setup'sDetails:line is a plain sentence with the Windows error number when there is one, not the internal code. A pending restart reads "Restart your PC to finish setting up Pal computers." instead of "Nothing needs to be set up.", andnamzu computer setupsays it once without asking to continue.--jsonresults from a failed setup gain an optionalexitCode.namzu computer setupnow also says to run it again after the restart when setup steps are still waiting, and the setup hint no longer appears for a failure that setup cannot fix. - —
4367edc: Keep terminal and ACP conversations running when refused or truncated tool arguments produce an invalid presentation. A malformed write call no longer throws while drawing its label: the CLI uses the tool name, preserves the original error and lets the model repair its input and complete the turn. Valid tool views, tool validation and permission checks retain their behavior.
- —
d521813: On Windows, the interactive screen now re-measures its terminal every 250 ms, so a Namzu terminal tab that is narrowed or split redraws to the new width instead of keeping the width it started with (lines cut mid-word, a message box with its right edge in the wrong place). Nothing changes on other platforms. The footer also shortens a Windows path at a backslash and shows the home folder as
~fromUSERPROFILE. No option or output format changes. - —
1fa60b9: The terminal app no longer opens with "Computer use is unavailable in this session: The openai provider cannot return images in tool results…" when the chosen provider cannot show screenshots. The reason still travels with the computer-use tool, so the model tells you the moment computer use is actually asked for. A desktop that cannot start at all is still reported when the session opens.
- —
0ef4884: On Windows, reading a Pal computer's status no longer starts the Linux machine Pal computers run on. Namzu Desktop reads that status every few seconds while a Pal's panel is open, so the machine used to start again right after it was stopped for being idle. Status now reads what setup recorded; starting the computer still starts the machine. Nothing changes for Docker or Podman.
- —
9d422c6: On Windows with the wslc engine, a Pal computer status that cannot be read now reports "not ready" with the setup hint instead of probing the engine, because that probe starts the Linux machine (which may have just been stopped for being idle). Nothing changes for Docker or Podman.
- —
adb0498: The terminal app no longer tells you to check your internet connection when a server on your own computer or network (Ollama, LM Studio, a local address) is down: it names the server and says to start it. Startup problems (the working folder, saved data, plugins, computer use, the browser) now print one plain sentence, with the error text shown only under
--debugas "Details:". The setup screen says how a credential was found in words ("Signed in with Codex") instead of an internal name, and a long folder name in CJK or with emoji is shortened by its display width. Nothing to change on your side; scripts that matched the oldWorking directory is unavailableorFailed to construct <provider>text should match the new sentences. - —
6f2eb8e: On Windows, folders in the terminal app's sentences (the trust prompt, saved-file notices,
/status) use backslashes like the footer does (~\Work\clients\leaf-folder). The trust prompt now wraps to the box's real width, so at 54 columns it no longer leaves "files" alone on a line, and its title, sentences and key line break evenly at every width from 40 up. Nothing to change on your side. - —
0888f71: An approval request for a write that would leave the file exactly as it is now carries a file preview with
beforeequal toafter, where it carried none. A client that treated a missing preview as "cannot show the file" can now say "no change". A client that drawsbeforeandafteras a diff shows an empty diff. - —
f0aeb9b: The npm package page of
@namzu/cliis rewritten for people who use the terminal app. Only the README changed; the CLI code and its behaviour are unchanged. - —
2e8f811: A conversation's title no longer includes the text of an attached file. Desktop sends an attachment to the model as
Attached text file: "name"after the typed words; the saved title now comes from the typed words only, and is the first file's name when only files were sent. A title that was already saved with attachment text keeps it until the conversation is renamed. - —
3c2bda7: The interactive screen now reads keys from the Windows console input device when
process.stdinis not a terminal but the output is, which is the case when the CLI runs as Electron's plain-interpreter mode inside a pseudo-console (a terminal started by the desktop application). Before, it stopped withRaw mode is not supported on the current process.stdin. An ordinary launch, and every non-Windows launch, keepsprocess.stdin; nothing to change on upgrade. - —
2d2a6c3: On Windows, opening a Pal or a conversation no longer starts three Windows processes (
whoami,icacls,icacls /save) every time it checks that a state folder is private. The check still runs the first time this process meets a folder, and it runs again when the path names a different folder than before (another device, file id or creation time), so a folder replaced under the same name is re-proved. What changed is that a folder this process already proved private is not re-proved while it is still the same folder, so an access-list edit made to that same folder while Namzu runs is no longer noticed until the next start. The signed-in account's id is also asked for once per process. Nothing changes on other systems. On a Windows machine with antivirus scanning, this removes seconds from opening a Pal. - —
0de3571: The model pickers (TUI and Desktop catalogue) list Zen's free models before the ones that need an API key and show them under separate "Free" and "API key" headings (a model with no published price follows under "Other models"; a list with only one group draws no headings and keeps an "(API key)" note on its key models), and mark a model whose limits the catalogue has not published yet with "Limits not published yet". Nothing to change on your side; only the order and headings of the Zen list and one extra note differ. The Desktop catalogue row gains an optional
groupfield ('free'or'key', Zen only). A client that ignoresgroupdraws no headings and loses the "(API key)" marker on a list that has both groups, because the per-row note is dropped once the headings carry it. - —
815433f: Fix installed Codex conversations rejecting effort choices offered by their native model catalogue. SDK admission now receives the discovered effort capabilities; dispatch still checks each selection against the current selected model before sending a native turn.
- —
9279c97: The terminal agent's composer is usable as soon as the saved or automatic model is known, instead of after the provider's model list (
GET /models) answers. With a gateway or local server that answers in 1 second, the composer used to appear 1 second later; now it appears at the same time as with a fast server. The reasoning-effort menu follows when the list arrives; a message sent before then uses the chosen model and the provider's default effort (a launch--effortapplies once the menu arrives), hypermode pins its effort when the menu arrives, and a list that fails leaves the effort menu out without a message.namzu execand ACP session creation still wait for the list, as before.AgentSessionOptionsgains an optionaldeferEffortCatalogue; leave it off to keep the old behaviour. - —
479adc4: Reopened ordinary Desktop conversations now keep an assistant reply you stopped mid-answer. Previously the partial text was visible live but vanished after a restart; it is now restored from the saved journal and shown as stopped. A stopped reply that had no text, or that was later replaced, still adds nothing, and Pal chat history is unchanged. The
namzu/conversations/historyresult gains an optionalstopReason: 'cancelled'on assistant rows; existing clients that ignore unknown fields keep working and nothing needs to change. - —
24cb6b4: Distinguish a subscription credential removed or cleared by its owner from a remote authentication rejection in desktop model catalogues. Show a fixed sign-in-unavailable notice without exposing owner paths or provider diagnostics. TLS and network failures retain their catalogue-loading notice.
- —
7e1068a: Do not display a tool-only assistant record as a media message when reopening ordinary desktop history. Text accompanying tools and actual media placeholders remain visible; the model's complete recorded history is unchanged.
Desktop task, retry-status, and metadata ownership reads no longer synchronize the installation-wide session index. ACP history loading verifies and folds one scoped journal snapshot without preparing a provider. Retry inspection retains the authenticated application home, trust, project, tenant, Pal claim, and checkpoint checks; actual retry and write admission remain enforced. Existing injected storage openers are preserved until the host supplies a direct-read opener.
- —
2ae7169: The desktop app shows a failed reply with the same quiet row a finished reply ends with: Retry and Copy as icons, other recovery actions as small muted text, Details as a text toggle and the time (leading, as on a finished reply) on one line, instead of filled buttons and a native disclosure. No flag, setting or stored data changes.
- —
292ed84: Read desktop conversation history through one fresh owned session scope without opening or synchronizing the installation index. Authorization, strict journal reads, and Pal reply filtering use the same admitted application home, with folder trust rechecked before reading the journal. Project, tenant, and Pal ownership checks remain enforced; conversation listing and other operations retain their indexed behavior.
- —
3727e2b: Show only the provider's actual listed model rows in the desktop catalogue. Do not insert unavailable registry defaults or saved models as available choices. Keep the current route visible in provider status and report omitted, failed, unsupported and timed-out catalogues with explicit notices. Credential rejections receive a safe authentication notice without remote diagnostics. Recognize a typed rejected subscription refresh grant as an authentication failure while keeping TLS and network failures distinct from invalid credentials. Use the driver's strict account listing when provided instead of mistaking a bundled fallback for a live account catalogue.
Check the selected model's credential access and supported wire before closing the previous conversation session. An invalid anonymous Zen switch now leaves the previous session intact, and a new conversation can select an eligible free model before preparing an unavailable saved provider.
Fix model preparation on a newly created ordinary ACP conversation before its first journal exists. Authorize only a session actually published on the same connection with the exact canonical workspace; unknown IDs, foreign workspaces and stopped slots remain refused. Pal conversations still require their durable ownership claim.
- —
a32c679: Preserve a stored assistant message's explicit commentary or final-answer phase in desktop conversation history when its public text still matches the recorded parts. This keeps progress narration in the work details after reopening an ordinary conversation. Pal history also omits an unchanged interrupted reply only when its own durable completion records cancellation; completed earlier replies and revised or legacy messages remain visible. These display projections leave journal messages and model replay unchanged.
- —
e7c61d2:
namzu exec "<prompt>"no longer adds its quarter-second wait for piped input to the start of the turn when stdin is a pipe nobody writes to (a CI step, a script, a host that spawned it and kept stdin open): the wait now runs alongside the rest of the startup, which took the request about 150 ms to 250 ms sooner in that case. The rule itself is unchanged: input that begins within a quarter second ofexecfirst looking at stdin is read to its end and appended to the question, andexecwithout a prompt argument, or with-, still waits as long as the pipe takes. Only the quarter second is promised: a producer that is slower to write its first byte (for example a slowcurl | namzu exec "summarise") may or may not be picked up, depending on how busy the rest of startup is; put the text in the prompt if you depend on it. - —
e366729: The first model request is smaller:
computer_use,browser,browser_act,list_palsandsend_pal_messageare no longer sent while they cannot work (the provider cannot return images or the desktop host is unavailable, no browser engine can run, the owner has no Pals), and are sent again as soon as they can. About 1,150 tokens fewer in the terminal on a machine with none of those. Nothing a working tool does changes; a model that names a suspended tool is told it is suspended instead of receiving its own refusal text. - —
a923f6b: Check complete Godot input batches before the original MCP handler and runtime WebSocket send. Invalid nested boolean strings previously aborted the pinned runtime coroutine and could leave earlier input dispatched without a final reply. Return a field-specific, repairable
not_dispatchederror instead, without coercing values or sending any event from the refused batch.Advertise the accepted boolean/number constructor types separately for each input event in live tool listings. Use literal
pressed: falseto release a key, mouse button or action; actionstrength: 0does not release it. Retain open dictionaries and encoded top-level event compatibility without adding required fields or injected defaults, and keep all other tools unchanged.Preserve valid Godot constructor inputs, optional defaults, top-level server coercion and ignored application dictionary keys. The generic SDK dictionary schema remains unchanged. Existing unknown application effects still require computer stop; validation never clears that barrier or replays input.
- —
c8613e0: Clarify Godot input timing in the installed bridge's tool description. Put a bounded hold duration on the press event's
delay_after_ms, or on the release event'sdelay_before_ms; a delay after release waits with the input already released. Show a 250 ms press-hold-release example and distinguish empty key events from waits and composite click's internalclick_delay_ms.Argument types, defaults, dispatch behavior and pinned upstream source remain unchanged. Update an existing guest bridge from the shipped asset while idle, then reconfigure normally to load its updated description.
- —
b0b6afa: The Desktop model list writes a GPT display name such as "GPT-5.6-Sol" as "GPT-5.6 Sol", the way the Codex app does. Only names that start with "GPT-" and end in "-<Word>" change; model ids sent to providers, and every other family's labels, are unchanged. Nothing to do on upgrade.
- —
e03a000: On Windows,
namzu upgradenow removes thenamzu.ps1wrapper that npm writes next tonamzu.cmdafter each update (only when it belongs to@namzu/cli). PowerShell prefers that wrapper and the default execution policy refuses it, so without this, typingnamzufailed with "running scripts is disabled" after every upgrade; now it runsnamzu.cmd. Nothing changes on macOS or Linux. Whennamzu upgradecannot tell which installation it is running from, its message now links the installation guide (https://github.com/cogitave/namzu/blob/main/docs/cli/installation.md) instead of a README section that no longer exists. - —
7f3b74b: Reset Codex reasoning effort to the selected model's actual native default when Default effort is selected, including after a model change. Refuse an omitted effort when the native model catalogue supplies no valid default, rather than silently retaining a previous turn's effort. Admit the native Codex review modes already advertised by its adapter; Claude Code retains its supported Ask first and Plan modes.
Retain the actual completed native final-answer identity in Codex terminal receipts and history reconciliation so successful turns no longer erase streamed replies when the terminal notification contains no text.
- —
c1027fb: Add a connected computer observation to
PalSystemPromptOptions.computer, with the current control mode, so hosts can describe an existing device independently of permission to execute tools.Fix Pal chat composition when the operator holds its connected computer. The Pal now receives its actual connected/control state instead of being told the computer is unavailable. Operator-held chat retains zero guest tools and performs no guest inspection or allocation; Return control admits the existing guest capabilities on the following turn.
- —
4e5693f: Continue without this reply now works in a Pal conversation. The Pal session had no way to close a paused turn, so the Desktop app showed "This connection cannot close paused turns." and left the failed reply in place. Nothing to change on your side. The two refusals a host can still give for a paused reply are now plain sentences.
- —
a923f6b: List Pal conversations with one per-call session-index synchronization instead of reopening and synchronizing the entire index for each candidate. Membership, current workspace identity, pinned revision, tenant, project, exact working directory and archive state remain validated from fresh profile and journal reads before the output limit. Standalone membership checks and ordinary-workspace or alias refusals retain their existing behavior.
- —
d8d562e: Add
buildPalSystemPromptandPalSystemPromptOptionsfor hosts to compose a Pal's saved identity, conversational language, public output discipline and truthful computer availability. The helper does not acquire execution authority. CLI Pal sessions use these shared instructions while retaining their pinned model and execution policy. Authenticated name and appearance edits update the next turn's display identity without rewriting the original introduction.Add
PalRuntime.admitConversation,PalConversationAdmissionandPalRuntime.computerChanging. Hosts can admit exclusive model-only chat without starting a computer, with current pause, cancellation and ownership checks. Guest acquisition remains separate and requires the real guarded environment; the existing mandatoryadmitcontract is unchanged. CLI manual chat supplies zero tools while offline or operator-controlled, then adopts the actual guest toolset on a later turn after explicit startup or returned control. Directed dispatch and checkpoint resume continue to require guest admission. The standalonepal chatcommand keeps its existing computer-startup preflight.Add
palConversationGreetingandPalConversationGreetingto reconstruct a stable English onboarding prelude from an owned conversation's original profile revision. CLI Pal claim and list results expose this separate host-authored intro without starting a model request or manufacturing a journal turn. Pal desktop history omits explicit assistant commentary and tool-call narration from its public display projection while retaining the original journal. - —
56d88dc: Restore the existing durable planning tools and bounded task context to Pal computer-work conversations. Tasks survive reopening and checkpoint resume in the same conversation; offline chat remains tool-free and planning does not grant host filesystem or computer access.
Use the task tools' compact activity labels for active calls and receipts in terminal and desktop views, including resumed turns.
- —
a08ad64: The terminal app's notices for a provider that cannot use tools, images or documents, and for a repaired conversation history, are now plain sentences that name the provider by product name ("Ollama can’t use tools, so Namzu will only chat while you use it. Switch to another provider to let Namzu work with files and commands.") instead of "Capability warning (tools): Provider 'ollama' does not support tools — 42 registered tool(s) were stripped…". A repaired history says what happened in words, without counts, and the Zen free-model rows read "experimental, and Zen may refuse them". The setup screen and the provider list are reworded the same way and wrap without a stray leading space. The previous wording still appears under
--debug. Exported transcripts use the new sentences. Scripts that match the old transcript text should match on the new sentences, or run with--debug. - —
f74677f: Provider failures and key checks in the desktop app read in plain words. A model the account cannot use no longer blames the key, and an account out of credit, a timeout, a missing model and an oversized message each say what happened. The CLI host's
namzu/providers/testcan now answer an unchecked key with acause(quota,busy,down,no-answer); callers that ignore it behave as before. The model list's failure sentences name the provider, and the signed-out Codex message now reads "Codex needs you to sign in with its own account." - —
e447a47:
tool_completedgains an optionalsavedPresentation: one label line (at most 200 characters) or a command's first line, never the output, journaled for every call that has no diff, and for an external engine's tools from a name map. Nothing existing changes:presentationkeeps its diff and cancelled/declined shapes, and live hosts that ignore the new field behave as before. A host that replays history can readpresentation ?? savedPresentationso a reopened conversation names its actions instead of showing a placeholder. The desktop host does. - —
8b9cb0a: Correct regression verification of the skill save confirmation when a long home path wraps the suggested destination label across terminal rows.
- —
800f80f: Launches after the first reuse V8's compiled code for Namzu's own modules, which takes about 100 ms to 150 ms off the interactive terminal and
namzu exec. The cache is kept incache/compileinside the Namzu home (~/.namzu, orNAMZU_HOME), and only when that folder already exists; a read-only or unwritable home just runs uncached. SetNODE_DISABLE_COMPILE_CACHE=1to turn it off, orNODE_COMPILE_CACHE=<dir>to put it somewhere else. Deleting the folder is always safe. - —
ab97512:
namzu --versionandnamzu --helpanswer in about 50 ms instead of about 480 ms. The command shell no longer imports every command and@namzu/sdkbefore it parses the command line; each command's module is imported when the command runs. Help text, option choices, exit codes and the output of every command are unchanged (the root help and every subcommand's--helpwere diffed against the previous build).Anything that imported
emitBootNarrativefrom the package's internalcli.jsnow finds it inboot-narrative.js; it was never part of the package's exports. - —
d8782dd: The interactive terminal and
namzu execstart about 150 ms to 250 ms faster. They no longer import@namzu/sandbox,@namzu/browser,@namzu/telemetryand the browser engine just to learn that those packages are installed; the launch-time capability line now reads the packages' manifests, and@namzu/browserloadsplaywright-coreon the first browser launch instead of when it is imported.One consequence: a capability package that is installed but fails to load is no longer reported at launch (the
namzu.capability.brokenrow). It surfaces where the package is first used, andnamzu doctorstill imports each package and reports it.probeOptionalPackagetakes an optional{ load: false }for the manifest-only answer, andprobeCapabilitiesuses it by default; nothing a caller of either passed before changes. - —
e447a47:
task_createdandtask_updatedsession events, and the ACPAcpTaskrow, gain an optionalactiveForm(what the task reads as while it is being worked on, such as "Running the tests"). It is absent when the model gave none, so nothing existing changes; a host that ignores it behaves as before. A host that draws a live plan can show it beside the task in progress. The desktop host passes it on when it lists a session's tasks. - —
56d88dc: Use the SDK's existing task context selector for interactive task snapshots. Failed tasks closed before the current turn no longer stay open or crowd out pending work; a failed dependency is terminal without being reported as successful. Preserve current-turn outcomes and the original start time during resume.
- —
ca8656b: The terminal app and the command line read in plainer words. Nothing about how they behave changes, and exit codes stay as they were (64 for a usage mistake). A script that matches the old text needs the new one:
- —An argument mistake such as
namzu --effort turbono longer prints commander'serror: option '--effort <level>' argument 'turbo' is invalid. Allowed choices are …. It prints“turbo” is not a valid value for --effort. Choose one of …, wrapped at the terminal's width, thenRun namzu --help to see what is available.; an unknown command readsUnknown command 'x'.. - —A command step in the transcript is headlined from the kernel's typed result (
ToolResult.facts):Ran npm test · exited with code 2, the command's own first and last lines with… N lines not shown, noSTDOUT:labels. A conversation saved beforefactsexisted keeps the old reading. A call you declined readsYou declined this, so it did not run. - —A picker or prompt prints its key hints once, in its own box, in one vocabulary (
↑↓ select · enter apply · esc back); the footer under the composer stays quiet while a box is open, and a hint line that is too wide loses whole hints, never half of one. - —“Turn”, “session”, “checkpoint” and “compaction” no longer appear in what a person reads: replies, conversations and summaries instead (
/configlists “Reply limits”). Sizes read48 KBand1.2 MB, counts are pluralised, no millisecond figures are shown, and the app’s errors give a sentence and a next step instead of a pasted message. First-run with no key saysChoose “Enter an API key” below, and subscription sign-in rows sayuses a one-time codeoropens your browser. - —Person-facing text uses ’ in contractions, never inside code, flags, paths or JSON.
The
.tsxfiles of the terminal app are now covered by the package's lint and format settings. - —An argument mistake such as
- —
e3b84d1: Closing a terminal on Linux now also ends a program that left the terminal's process tree straight away.
setsidstarted from an interactive shell forks and exits at once, so the program was reparented before the host could find it and kept running after the terminal closed. Every program started in a terminal now inheritsNAMZU_TERMINAL_ID, set to the terminal's id, and the host ends this user's processes that still carry it once the terminal has ended. This includes a background service first started from that terminal, such as atmuxserver orssh-agent, even if another terminal uses it by then. Nothing to change on upgrade; start a program that must outlive its terminal outside it. Windows is unchanged and adds no variable. - —
12c7682: The terminal app's key lines stay on one row, a cut list says what it hides, and a stopped start reads cleanly. Nothing about what a key does changes. A script that matches the old screen text needs the new one:
- —The line of keys at the bottom of the provider picker (and every other picker and dialog) is one row at any width, from 40 columns up. Where it used to wrap, it now drops its least important hints or says them short:
↑↓ or 1–9 selectbecomes↑↓ select,esc or ctrl+c exitbecomesesc exit,k enter an API keybecomesk API key.↑↓ select,enter applyand the key that leaves stay to the end. The sign-in and key-entry screens put their keys on a line of their own instead of at the end of a sentence. - —A provider list that is cut to the height of the terminal shows
↓ 3 moreunder the last row and↑ 2 moreabove the first, and keeps the row under the cursor in view. - —A
namzu.config.jsonthat cannot be read shows its sentence alone, without a leading·and without the second "Startup stopped / Resolve the error above, then restart Namzu." block, which stays only under errors that do not say what to do. A screen with no composer no longer printsshift+tab to cyclein its footer. - —Node 22's
ExperimentalWarning: SQLite is an experimental feature and might change at any timeis no longer printed above the banner. Every other Node warning still prints. - —The slash menu (and the file menu) fits its key line to the width it really has, so
tab completeno longer wraps at 60 columns or shrinks to a baretabat 40, and a command's description is at most two lines ending in…where it goes on, so the menu is no taller than a 40-column terminal. A model list that keeps the current model saysesc keeprather than losingenter apply, and "Could not list models" no longer ends in two full stops. - —The copy, edit and resume pickers, the permission prompt, the tool-output viewer and the provider-setup screen fit their key line to the room inside the box (two columns narrower than they assumed), so
esc cancelno longer wraps at 58 to 62 columns and the edit picker keepsenterandescat 40.
- —The line of keys at the bottom of the provider picker (and every other picker and dialog) is one row at any width, from 40 columns up. Where it used to wrap, it now drops its least important hints or says them short:
- —
5f7bb9b: The terminal app words three more things plainly. A
namzu.config.jsonthat is not valid JSON now reads "namzu.config.json isn't valid JSON (line 3, column 5). Fix it, then start Namzu again." with the line and column of the mistake; the parser's own wording is shown only with--debug. A read, search, write or edit step is headlined from the kernel's typed facts ("Read a.ts", "Searched for x · 2 matches in 2 files", "Edited a.ts +2 −1", "Created n.txt +2") instead of the model's receipt with a full path; conversations saved before those facts existed keep their old text. A provider picker shows its setup key once, in its bottom key line, instead of in a line of its own.If you match the old text of the config error (
<path> is not valid JSON: <parser text>), match the file name and "isn't valid JSON" instead.ConfigLoadError.messagefor invalid JSON carries the new sentence and the parser's text is oncause. - —
b8831a6: The terminal app reads a
namzu.config.json(and the user and managed settings files) saved with a leading byte-order mark, which Windows editors add, as if the mark were absent; it used to refuse the file as invalid. A settings file it cannot parse is now named the way a person finds it ("namzu.config.json in this project",~/.namzu/config.yaml, or the managed file's path) with the line and column, and the YAML message no longer carries the parser's wording unless you pass--debug. The provider list no longer squeezes a source to one character at 40 columns: below 63 columns the source is drawn under the provider's name. In the model list a long model name is no longer cut short to make room for "(current)" and "(default)"; the marks move to the line under it. No setting or flag changes. - —
899ed00: The terminal app no longer shows a provider's or program's own text where a plain sentence belongs. A model list that could not be fetched says, for example, "OpenAI rejected the saved API key. Press Esc, then k to enter a new key."; a key the provider rejects says so without the provider's message; provider setup shows "Installed · version 0.46.0" or "Couldn’t check Codex. Details: --debug" instead of the program's warning lines; and the folder trust prompt writes the folder with
~and a middle ellipsis that keeps the last folder whole. A failed model switch or effort check is plain too. The raw text is still available with--debug, after "Details:", with the request ID labelled. Nothing to change on your side. - —
bebe135: A tool result can now say what the call did as typed fields, so a host builds what a person sees from fields and not from the model's text. Nothing you already send or receive changes shape, the text the model reads is unchanged, and no code needs to change to upgrade.
New and optional:
ToolResult.facts, a closed union (ToolFacts) of a command's exit code and its stdout and stderr as head-and-tail windows (ToolTextWindow) or a note that the output was binary, a read's path and size, a search's and a listing's counts, whether a write created, updated or left a file, and the kind of a failure (ToolFailureReason). The built-inbash,read,grep,glob,editandwritetools set it,tool_completedand the ACPtool_callupdate carry it, a genericToolCallViewmay carryfactsfor what the call is about, andadmitToolFactsreads a saved one back. A call refused without running, an unknown tool and a thrown filesystem error also carry afailurefact.execHostShelladditionally returnsstdoutBytesandstderrBytes. A post-tool hook that replaces the output also dropsfacts, so a redaction is never undone.If you build a view of tool output yourself, read these fields instead of the
STDOUT:andSTDERR:labels, the "Found 2 match(es)" and "1 replacement(s)" sentences and the notes about withheld credentials or shortened output, which are the model's interface and are not stable text to parse.The Desktop app now words every tool row from those fields (Read a.ts, Searched for export, Ran npm test · failed (exit 3), Couldn't read missing.txt: file not found), draws a command's colours, folds long output to its first and last lines with Show more, says "Binary output, 3 KB" instead of printing the bytes, counts a finished edit in the reply's changes card, shows paths in approvals relative to the project, and leaves "0s" off work that took under a second. The CLI host restores a saved conversation's
facts(without a command's output) so a reopened row reads the same. - —
ea9e812: The terminal app now draws in a terminal even when
CIor similar variables are set in the shell (dev containers, inherited terminal tabs); before, the screen stayed blank until exit. The variables are not changed for the commands the agent runs. Nothing to do on upgrade. - —
f7f48f9: Secure and verify private state and credential files on long Windows paths by passing extended absolute paths to icacls. Keep the existing current-user ACL proof and fail-closed behavior. Project and conversation directory names remain compatible with existing installations.
- —
f7f48f9: Fix operator
!commandexecution on native Windows by using its platform shell instead of/bin/sh. Preserve/bin/shon POSIX, decode split UTF-8 output, refuse pre-aborted launches, and share host process-tree cancellation. Transcript output remains capped at 20,000 characters; a command is also stopped if either stream exceeds 80,000 bytes. Process-start failures now remain visible in the transcript. - —
afd7ba4: Show
namzu.cmd resumeon Windows when the npm command wrapper belongs to the running installation. Keep explicit Node and entrypoint commands for alternate installations and source launches. Scheduled-run resume commands now use PowerShell 5.1-compatible directory changes and quoting on native Windows, preserving the job's project and additional directories.Scheduled-task guidance now distinguishes tool permissions from available tools, local HTML files from artifact services, and cumulative token budgets from context-window size so proposals do not promise unavailable tools or outputs.
- —
Updated dependencies [dd7ee4b]
- —
Updated dependencies [4314f90]
- —
Updated dependencies [b8852a1]
- —
Updated dependencies [7f5ac50]
- —
Updated dependencies [7f5ac50]
- —
Updated dependencies [56d88dc]
- —
Updated dependencies [3727e2b]
- —
Updated dependencies [1af4531]
- —
Updated dependencies [efb7b06]
- —
Updated dependencies [84452ec]
- —
Updated dependencies [53aed2a]
- —
Updated dependencies [6194bda]
- —
Updated dependencies [301f5b3]
- —
Updated dependencies [6194bda]
- —
Updated dependencies [56d88dc]
- —
Updated dependencies [f7f48f9]
- —
Updated dependencies [9ef6506]
- —
Updated dependencies [56e846c]
- —
Updated dependencies [2057fd6]
- —
Updated dependencies [c8613e0]
- —
Updated dependencies [6f46da3]
- —
Updated dependencies [be9f8b6]
- —
Updated dependencies [c1027fb]
- —
Updated dependencies [24cb6b4]
- —
Updated dependencies [b6ea5ca]
- —
Updated dependencies [bb0ad2e]
- —
Updated dependencies [d5030ca]
- —
Updated dependencies [7f5ac50]
- —
Updated dependencies [7473449]
- —
Updated dependencies [6194bda]
- —
Updated dependencies [6f46da3]
- —
Updated dependencies [f4b7d1d]
- —
Updated dependencies [6ff961f]
- —
Updated dependencies [023224a]
- —
Updated dependencies [56d88dc]
- —
Updated dependencies [7f5ac50]
- —
Updated dependencies [6f46da3]
- —
Updated dependencies [5e2fb3a]
- —
Updated dependencies [c82b951]
- —
Updated dependencies [0f5aa96]
- —
Updated dependencies [a5dbeb2]
- —
Updated dependencies [2d5d18d]
- —
Updated dependencies [84a9472]
- —
Updated dependencies [c1027fb]
- —
Updated dependencies [d8d562e]
- —
Updated dependencies [53aed2a]
- —
Updated dependencies [81d1721]
- —
Updated dependencies [ce52b90]
- —
Updated dependencies [84a9472]
- —
Updated dependencies [f7f48f9]
- —
Updated dependencies [815433f]
- —
Updated dependencies [4367edc]
- —
Updated dependencies [6ff961f]
- —
Updated dependencies [6f46da3]
- —
Updated dependencies [f7f48f9]
- —
Updated dependencies [4367498]
- —
Updated dependencies [7f5ac50]
- —
Updated dependencies [ad2273f]
- —
Updated dependencies [846557b]
- —
Updated dependencies [56d88dc]
- —
Updated dependencies [53aed2a]
- —
Updated dependencies [12b5af9]
- —
Updated dependencies [42ef5e4]
- —
Updated dependencies [9d422c6]
- —
Updated dependencies [eddf7aa]
- —
Updated dependencies [f9f672b]
- —
Updated dependencies [fc86c05]
- —
Updated dependencies [e447a47]
- —
Updated dependencies [093d657]
- —
Updated dependencies [57c99b2]
- —
Updated dependencies [c590142]
- —
Updated dependencies [1fa60b9]
- —
Updated dependencies [f0aeb9b]
- —
Updated dependencies [5a0a1f7]
- —
Updated dependencies [d8782dd]
- —
Updated dependencies [3727e2b]
- —
Updated dependencies [3727e2b]
- —
Updated dependencies [e447a47]
- —
Updated dependencies [56d88dc]
- —
Updated dependencies [98e8469]
- —
Updated dependencies [a2a4b17]
- —
Updated dependencies [bebe135]
- —
Updated dependencies [f7f48f9]
- —
Updated dependencies [4367edc]
- —
Updated dependencies [2395a85]
- —
Updated dependencies [01b926c]
- —
Updated dependencies [4367edc]
- —
Updated dependencies [134932b]
- —
Updated dependencies [0de3571]
- —@namzu/sdk@50.0.0
- —@namzu/openai@4.1.4
- —@namzu/google@1.1.2
- —@namzu/sandbox@25.0.0
- —@namzu/browser@2.0.1
- —@namzu/anthropic@6.3.0
- —@namzu/zen@4.0.0
- —@namzu/computer-use@3.1.0
- —@namzu/ollama@2.2.5
- —@namzu/openrouter@3.0.3
- —
Major Changes
- —
b7d83ec: An MCP server that finishes connecting or discovering tools after its startup deadline is now closed after that late result. The configured
connectTimeoutMsapplies to connection and discovery as one budget, rather than allowing each phase the full time separately. Servers whose combined handshake and discovery took longer than the configured deadline but whose individual phases each fit previously connected; raise that server'sconnectTimeoutMsif it needs more time. Timed-out servers remain named failures and cannot leave an unreported live connection behind. Values above Node's maximum timer delay of 2,147,483,647 ms are now refused by server name; lower any such value to that limit or less. - —
b7d83ec: Plugin
user_prompt_submitannotations now reach the model as user-role request context instead of system instructions. The CLI gives operator shell output and idle background-job exits the same request-context role. Hosts that used these observation channels to issue policy must move that policy to an explicit trusted instruction; the observations remain visible to the model on the current request.The
namzu mcp testHTTP 401 hint now uses a literal<name>placeholder instead of embedding the configured server name in a runnable shell command. - —
b7d83ec: Memory and saved resident state now reach the model as request-only user-role context rather than system guidance. In the SDK,
createMemoryRecallStepreturnsPrepareStepResult.contextinstead ofsystem; hosts that read or compose the hook result must usecontextand pass it through the normal preparation chain. Resident continuation, wake evidence, skill catalogues and learned text also move from systemdynamicorturncontributions tocontext; hosts that inspect placements must migrate.contextcontributions now render once beforeprepareStepso their estimated cost reduces preparation headroom; hosts relying on a contribution to observe preparation side effects in the same model step must move that logic to a later stage. The CLI moves file-backedUSER.mdandMEMORY.md, its stored-memory index, and automatic recall out of system prompts on new sends, resident turns and resumes. Interactive resident turns now place admitted data in the newSendOptions.extraContextrequest-only field; their fixed doctrine remains inextraSystem. Hosts that relied on memory files or resident data as system instructions must pass trusted guidance explicitly. This changes default provider-visible roles and may change model behavior and prompt-cache accounting; request-only context still uses input tokens on each request and the CLI bounds memory against estimated remaining request room. - —
b7d83ec: Plugin MCP server declarations now reject fields they do not implement, including
url,headers,inheritEnvandrequireApproval. Those fields were previously accepted and silently removed while the declared stdio command still started. Remove unsupported fields fromplugin.jsonor an in-code plugin; configure a remote server through the host's MCP settings and express approval in host source permissions. Plugin MCP startup now has a ten-second deadline across connection and discovery, configurable withconnectTimeoutMsup to one hour, and cleans up a failed connection before rolling back the plugin.Hosts supplying
ConfigRegistrymust move plugin reconnect-policy overrides frommcp.<server-name>tomcp.plugin.<plugin-name>.<server-name>; the old key was shared by unrelated plugins and is no longer read. AConfigScopenow hasdispose()to release its live namespace and watchers while retaining saved overrides. Plugin disable and failed enable release their scopes, and overlapping enable, disable and uninstall calls for one plugin id now settle in call order.Plugin MCP drift is now tracked by full plugin source id rather than bare server name: hosts that relied on a drift event between two unrelated plugins using the same local server label should compare those sources explicitly. The baseline now follows the one admitted listing actually mounted as tools, including reconnect and list-change refreshes; a second startup listing is gone.
Minor Changes
- —2b1cbb5: Show running background shells beside the composer and let operators inspect their retained output or stop a shell in
/jobs. Use/jobs listfor the previous text summary. - —b7d83ec: Add
namzu mcp test <name>to check the effective MCP server selected by the current profile and project. It connects only that server, reports the usable tool count or a safe failure reason, and closes the connection. The working directory must be trusted; pass--trustto accept it for this test only. After adding a server, run this command before starting an agent to find connection, authentication or tool discovery problems. - —2b1cbb5: Show submitted steering and next-turn messages in the TUI with their text and attachments. Alt+Up recalls the latest authored queued message, and Esc preserves pending input when it interrupts a turn.
Patch Changes
- —b7d83ec: Malformed
mcpServersentries with invalidargs,env,headers,inheritEnv, command, URL or working-directory types now fail by server name. One bad entry no longer aborts a session or prevents earlier successful connections from being closed. - —b7d83ec: The CLI test runner now uses at most four workers on many-core machines. This prevents healthy session and scheduled-run tests from hitting their wall-clock timeout under heavy test contention; CLI runtime behavior and APIs are unchanged.
- —2b1cbb5: Hosts can use
QueryParams.onJobNoticeDelivered(jobIds)to distinguish background job exits recorded for the model from exits that still need delivery on a later turn. The CLI now keeps a late exit pending for the next turn, retains it after a failed or aborted send, and shows its transcript row once. - —Updated dependencies [b7d83ec]
- —Updated dependencies [b7d83ec]
- —Updated dependencies [2b1cbb5]
- —Updated dependencies [b7d83ec]
- —Updated dependencies [b7d83ec]
- —Updated dependencies [b7d83ec]
- —Updated dependencies [b7d83ec]
- —Updated dependencies [b7d83ec]
- —Updated dependencies [b7d83ec]
- —@namzu/sdk@49.0.0
- —@namzu/browser@2.0.0
- —@namzu/computer-use@3.1.0
- —@namzu/anthropic@6.2.2
- —@namzu/ollama@2.2.5
- —@namzu/openai@4.1.3
- —@namzu/openrouter@3.0.3
- —
Patch Changes
- —
57ae7c7: Background jobs whose process cannot start now emit one exit update after the child closes, without reporting the spawn error as a process exit code. An error on a process that already started no longer marks its job as exited while it is still running. SDK API signatures are unchanged.
Completed and aborted job or delegated-task waits release their polling timers instead of keeping them alive for the rest of a long session. Idle-stream, agent front-door, awaited-job and large-input lexer regression tests now avoid real-time races that could fail on a busy CI runner.
The CLI's background-child and scheduled-run integration tests now wait for the event they assert, leaving their normal process deadline to Vitest instead of letting a short poll or unrelated watchdog decide the outcome.
- —
b53bdb2: On Windows, the PowerShell installer now invokes npm and Namzu through their
.cmdshims, so a Restricted execution policy does not block their.ps1wrappers. A successful npm install that writes a warning to stderr no longer aborts the installer. It requires the CLI's actual minimum, Node 22.13. Provider diagnostics now name the Claude Code credential files checked when no Claude session is available, and/setupexplains that Desktop sign-in does not guarantee a reusable Code CLI session. Command Prompt installation instructions usenpm.cmd. - —
Updated dependencies [57ae7c7]
- —@namzu/sdk@48.3.1
- —
Minor Changes
- —f0505b5: Anonymous Zen users can now select all eight bundled free models instead of only Space Bunny. The seven previously blocked models use an experimental OpenCode-compatible request identity on the official Zen endpoint; Space Bunny keeps its verified direct request path, and keyed Zen and Zen Go requests are unchanged. Zen may still reject an experimental model or route, so a free catalogue entry is not a guarantee of service. The CLI labels this access as experimental and keeps Space Bunny as its default.
Patch Changes
- —Updated dependencies [f0505b5]
- —@namzu/zen@3.1.0
Major Changes
- —
29b142f: Zen Go no longer includes
glm-5.1,qwen3.6-plus, orqwen3.7-maxin its bundled supported model roster. The service still lists these IDs, but its current documentation provides no route or price for them. Applications relying on these IDs should select a documented model or, if they have independently verified the wire, configureZenGoProviderwith an explicitprotocol. The CLI no longer offers these reviewed omissions.The default anonymous Zen model changes from
muse-spark-1.3-contributor-freetospace-bunny-freein both the SDK and CLI. Callers that need the old choice must passmodel: 'muse-spark-1.3-contributor-free'explicitly and try credentialed Zen access, subject to gateway admission. The gateway currently rejects direct keyless Namzu requests to Muse and six other documented free models with HTTP 403; only Space Bunny Free completed a live keyless text and tool-continuation test. Anonymous model discovery now admits only that verified model while retaining the other free models for credentialed use.Zen and Zen Go now follow the reference client's route overrides for DeepSeek V4 Flash and MiniMax, handle Muse Spark reasoning continuation without replaying gateway-bound ciphertext, and read the current Zen Go price table when refreshing the catalogue. These keyed routes have wire-fixture coverage but were not live-tested with an account. The CLI distinguishes model listing from verified inference and explains the gateway's specific HTTP 403 free-tier refusal.
Patch Changes
- —Updated dependencies [29b142f]
- —@namzu/zen@3.0.0
- —
Patch Changes
- —c926384: Hypermode now appears at the composer’s upper right. Activating it briefly sends a soft lavender glow from the input border’s center toward both sides; the border then rests in graphite. The footer no longer repeats the label.
Minor Changes
- —072bb74: Windows computer use can capture pixels for one named window with
screenshot {window_id}and keep background clicks and drags, text and keys bound to that window. Windowtype_textandkeycan explicitly requestdelivery_mode: 'foreground'for browser text or modifier shortcuts that cannot run in the background; this may briefly bring the verified window forward. The pinned driver setswindowScroll: falseand refuses scoped pixel scrolling because its wheel input cannot prove which window receives it; a host that supports safe window scrolling can offer it. Capture identity and window geometry are checked before input; moved windows, old captures and restarted driver sessions require a new window screenshot. Hosts can offer window capture without display capture, in which casewindow_idis required and plain display screenshots remain unavailable. A failed window capture never broadens to a display capture. When both browser and computer-use tools are ready, the CLI routes a named existing browser window to computer use, leaving browser tools for the separate Namzu-managed profile.
Patch Changes
- —d3e35af: Continuing a turn paused for a person no longer tells the model that the sign-in or challenge was completed. The model is told the operator chose to retry, and to stop if the tool still needs a person.
- —Updated dependencies [d3e35af]
- —Updated dependencies [072bb74]
- —@namzu/browser@2.0.0
- —@namzu/sdk@48.3.0
- —@namzu/computer-use@3.1.0
- —072bb74: Windows computer use can capture pixels for one named window with
Patch Changes
- —6cdf009: Show the session's hypermode label once in the composer footer. Turning it on now briefly sweeps colour across the message frame, then returns to a still border; screen-reader and colour-disabled terminals keep the static display.
Minor Changes
- —
25966a5: Scheduled script proposals now explain the permission set they actually need, including why
read-onlyblocks every script and why a home-directory working folder is refused. The CLI shows a compact, scrollable confirmation for model proposals and/scheduleconfirmations, with the exact source available for review. It labels pure-script permissions accurately and explains a blanketbashdenial without suggesting a script rewrite.New
notifyOnFinishon the schedule tool and--notify-finished true|falseon the CLI let a polling job suppress Namzu's generic success notice while keeping failure notices. The default remainstrue; existing jobs keep their behavior until changed.
Patch Changes
- —25966a5: Accept the first selection key after a choice menu appears. A goal menu could previously ignore Enter immediately after it became visible, leaving the objective editor unopened.
- —25966a5: Keep the composer ready for the first keystroke after a text prompt closes. The previously hidden composer could briefly lose that keystroke while its input listener was being reattached.
- —25966a5: Use plain text labels in terminal output where emoji could crowd the next field. Scheduled jobs now appear without a clock prefix, and older saved run titles display with
Scheduled:in conversation lists and notices without changing stored sessions. - —Updated dependencies [25966a5]
- —@namzu/sdk@48.2.0
- —
Major Changes
- —3a0daac: Gemini API keys pasted into Namzu are now saved automatically in its private credential store and reused on later launches. This changes the former session-only default. To keep a key out of Namzu's store, provide it through a per-process
GEMINI_API_KEYenvironment variable instead of pasting it into the picker.namzu logout geminior/logout geminiremoves the saved key; an environment variable remains under your control.
Minor Changes
- —3a0daac: Archived conversations can now be listed and restored in their original project with
/unarchiveornamzu archive list|restore. Restore one before usingnamzu resume <id>; the conversation history stays in its original log and remains scoped to its project. Long-lived CLI sessions refresh their project archive listing when another process archives or restores a conversation. - —3a0daac: Add
namzu mcp list|get|add|removefor user-owned tool servers. Existing server config continues to work, including names outside the new-entry naming rule. These commands write~/.namzu/config.yaml, preserve other settings, accept environment-backed Bearer headers, require HTTPS for URL queries or credential headers outside loopback, and hide credential-bearing values in their output. HTTP summaries show only the origin, keeping tokenized paths as well as queries out of text and JSON. User-config writes replace even an existing broadly readable file with one restricted to its owner. - —3a0daac: Add
namzu mcp login <name>andnamzu mcp logout <name>for configured HTTP tool servers. Login uses the official MCP OAuth authorization-code flow, a loopback or pasted callback, PKCE, callback state and authorization-server issuer checks. The CLI stores the resulting Bearer and refresh tokens privately for that exact server URL. Ordinary tool calls reuse or refresh the saved grant without opening a browser; a configured Authorization header continues to take precedence. HTTP authorization refusals stop connection setup instead of falling back to a legacy handshake. - —3a0daac: Allow delegated CLI agents to opt into a separate managed Git worktree with
workspace: "worktree". The child runs from the selected checkout's committed HEAD, preserving the parent's directory beneath the Git root. The worktree remains available for review after any outcome. SDK callers can choose a per-child shared or isolated workspace and explicitly retain it; isolated requests accept a checked relativesubdirectory. A manager can make omitted choices shared withworkspaceDefault: 'shared'; existing SDK backend provisioning and cleanup remain the defaults. - —3a0daac: Add
namzu worktree create|list|fork|resumeand/worktreefor managed Git checkouts. Each checkout keeps its own conversation history; forking copies a settled conversation into the new checkout's project. Uncommitted source files stay in the source checkout, and Namzu never removes a worktree automatically. - —3a0daac: Add
namzu skills --auditto check which file skills the model can actually load. It exits with status 1 for an invalid enabled skill, reports the SDK loader's reason, and can estimate manifest overflow with--context-window <tokens>.
Patch Changes
- —c8bb87a: When a browser returns an HTTP authentication challenge, the terminal handoff and
namzu browser loginoutput ask the operator to check access without claiming that a sign-in or password screen appeared.browser logindoes not update the profile's "last sign-in" time from that challenge alone. Other browser handoff messages keep their existing wording. - —3a0daac: Reflow completed conversation text when the terminal expands, preserving the draft and one copy of history. Show when the model picker has more models above or below its visible rows; wider terminals count hidden models. Pause and error messages name the provider that reported a failure when available. A paused turn now tells operators to use
/resumeor/abandon, and switching providers keeps dependent prompts held until the turn is successfully resumed. - —Updated dependencies [c8bb87a]
- —Updated dependencies [c8bb87a]
- —Updated dependencies [c8bb87a]
- —Updated dependencies [3a0daac]
- —Updated dependencies [3a0daac]
- —Updated dependencies [3a0daac]
- —Updated dependencies [3a0daac]
- —Updated dependencies [3a0daac]
- —Updated dependencies [3a0daac]
- —@namzu/browser@1.0.0
- —@namzu/sdk@48.1.0
- —@namzu/computer-use@3.0.0
- —3a0daac: Gemini API keys pasted into Namzu are now saved automatically in its private credential store and reused on later launches. This changes the former session-only default. To keep a key out of Namzu's store, provide it through a per-process
Major Changes
- —
567ada8: CLI MCP tool names change from
mcp_<server>_<tool>tomcp__<server>__<tool>. Update any permissions, saved prompts or integrations that refer to those names; the CLI warns once for each configured permission rule still using the old form. MCP tools now update after server notifications and reconnects. Configureallow,deny,maxRetries,requireApproval,readOnlyHintTrustedandinstructionsper server undermcpServers;instructions: trueincludes the server's text as labelled untrusted request context./mcp toolsshows the server's current tool names and instructions.The SDK now skips prompt discovery when a server did not advertise prompt support and keeps approval-wrapped tool definitions stable across unchanged toolset snapshots.
- —
567ada8: Plugin tools now carry a source for their owning plugin (
plugin:<name>) or MCP server (plugin:<name>/mcp:<server>), so source authorization can distinguish them. Plugin MCP servers usemcpToolset, gaining live tool and prompt discovery, policy-filtered deferred resources and held changed definitions. Plugin MCP prompt tool names change from<plugin>__mcp_prompt_<server>_<name>to<plugin>__mcp__<server>__prompt__<name>; update saved tool names and permission rules that refer to them.MCPToolsetOptions.reconnectalso accepts a function that returns the current reconnect policy. - —
fbeac55: A scheduled job can now be a fixed shell script instead of a model prompt, or a cheap "wake-gate" script that decides whether to run the model at all —
namzu schedule add --kind script|script+agent, or the model'sscheduletool withkind/script. Ascriptjob spends zero tokens and opens no session; ascript+agentjob runs a gate script first and calls the model only when it prints{"wake": true, "context": "..."}. The script body is checked against the scheduled-run floor (the whole text, exactly as a livebashcall is read) and then everydenyrule — the operator's own and any config file's — per lexed command;allow/askrules andunmatchedare never consulted for a script, since it is fixed, human-confirmed text, not a call a model improvises. Ascript+agentjob'sallow/ask/unmatchedrules still govern its AGENT phase only, unaffected by the script's own (deny-only) check.@namzu/sdk(major)- —Breaking type correction:
ScheduleJobPreview.modelis optional because a purescriptjob has no model. Hosts that display it must handle its absence forrunKind: 'script'; previews foragentandscript+agentstill carry the pinned model. - —
ScheduleJobPreview.networkAccessnow includes a host script or an allowed host shell that could reach the network, even without web/browser tool grants. Its new optionalnetworkGrantAccessseparates those grants from shell capability; a host implementingpreviewUpdateshould set it so a kind-only conversion can be checked against the network-beside-host-shell rule without refusing every script. An older host that omits it is treated conservatively usingnetworkAccess. - —An unattended sandbox-escape opt-in no longer waives review of a runtime-chosen program name or another call's outside-root path in the same batch.
- —Security fix: a recognised wrapper such as
env,nice,sudo,busyboxortoyboxcan no longer hide a shell's-cpayload from review. Literal payloads are read for permission checks; an expanding payload requires review and is refused in unattended runs.script -t -cnow recognises-cas the command option;-Tconsumes its required timing-file value. - —Security fix:
find's-execfamily now follows wrappers and shell-cpayloads inside each clause, including when another wrapper launchesfind. A dynamically selected program or afindplaceholder in a shell payload requires live review and is refused in an unattended scheduled script. The scheduled-run floor also sees service-changing tools behind wrappers in these clauses. - —Breaking:
ScheduleJobDraft.promptis now optional (prompt?: string), since a purescriptdraft has none. AnyScheduleToolHostimplementation that readsdraft.promptas a barestringneeds a null check (or to keep reading it only whendraft.runKindis'agent'/'script+agent', where it is still guaranteed present). - —
ScheduleJobDraftgainsrunKind?: 'agent' | 'script' | 'script+agent'(absent ='agent', unchanged from before) andscript?: { body, shell, timeoutMs? }.ScheduleJobPreviewgains the samerunKind/scriptfields so a host's confirmation screen can show the exact text. - —The
scheduletool's input schema gainskindandscript;createrequiresscriptunlesskindis'agent'and requirespromptunlesskindis'script', and refuses an agent draft that also carries a script. The existing network-beside-a-host-shell refusal now also covers ascript/script+agentjob on the host: the script IS shell code, not a rule a live call might reach later. - —Security/UX fix:
updateacceptedkind/scriptin its input schema but silently dropped both from the change it actually applied, reporting success with no error or warning that the model's new script was ignored.ScheduleJobChangesgainsrunKind/script(additive, so this alone would beminor) andupdatenow forwards both toScheduleToolHost.previewUpdate, re-verified fresh against the scheduled-run floor and everydenyrule exactly like a new job's, and shown in full — with what changed — on the same confirmation screencreateuses. A host that does not yet read the new fields offScheduleJobChangessilently keeps a job's existing kind/script on every update, the same as before this release; only@namzu/cli's own host (below) newly acts on them. - —Security fix:
updatechecks the effective preview's network grants after applying every change, so a kind-only conversion cannot inherit web/browser access beside a newly introduced host script. A call that changes the kind and leaves permissions unset is checked too. - —New exports from the root entry:
execHostShell,ExecHostShellProgress,CommandShell,CommandShellProbe,findCommandShell,hostCommandShell,hostShellSpawn,withoutBashStartup— the shell-resolution and spawn machinery thebashtool already used internally, now available to a host that runs a command line outside a live tool call. - —
findCommandShellForDialectandinstalledCommandShellForDialectselect an installed bash or sh explicitly, returning no shell if the requested interpreter is absent.execHostShellaccepts thatCommandShellthrough its optionalshelloption, keeping its existing process supervision and output capture for hosts that choose an interpreter themselves. - —Security fix, in the scheduled-run floor, so a live
bashcall benefits too:trap 'ACTION' SIGNALnow readsACTIONas a command line (it runs when the signal fires);find … -exec|-execdir|-ok|-okdir … ;|+is refused when its clause contains{}and the search root is unknown or could reach NAMZU_HOME, a-name/-pathfilter could match NAMZU_HOME's own name, or the clause's own program can stop or remove a service; running a file the SAME command line wrote earlier (>,>>,tee,cp/mv, then./x,sh x,bash x,. x,source x) is refused, since its content was never confirmed. - —
lexShellCommandLinenow reads$(…)and backtick bodies as nested command lines (the same treatmentbash -cpayloads already got) instead of calling the whole containing line opaque outright: each command inside a substitution is inShellLexResult.commands, markedorigin: 'substitution', checked by the scheduled-run floor and by everydenyrule exactly like the rest of the line. Two shapes stay opaque because what actually runs cannot be pinned down from the text alone, measured against real bash (5.2.21 and 5.3.15): a command substitution beside brace expansion in the same word ($(cmd){a,b}runscmdonce per alternative, not once) and an unquoted substitution used as a</>redirection target (a${var:-…}-style default value can run its substitution twice when the target turns out ambiguous) — both are refused the same way an unmodeled construct always was.${ list; }(bash 5.3's brace-form substitution) and process substitution (<(…),>(…)) are unchanged, still opaque; out of this scope.ShellWordgainssubstitutes: boolean(additive). Proven differentially against a real bash on the host (command_not_found_handleargv capture, disabled builtins, emptyPATH): every existing corpus line, the exhaustive up-to-three-token sweep, a 6,000-line seeded random sample (manually run to 150,000 with zero mismatches before landing), and new hand-written cases for nesting, mixed quoting, and the two constructs above.decomposeCommandLine'ssegments/opaquechange the same way for these two constructs. - —Security fix, follow-up to the above: a second review found that the scheduled-run floor's own
pkill/killall/systemctl/launchctl/schtasks/D-Bus detection read a wild (expanding) program-name word as NOT being the tool in question unless its raw, unevaluated text happened to contain the tool's name as one contiguous run —$(echo pk)ill -f nodeand$(echo system)ctl stop $(echo namzu)-scheduler.serviceboth passed. A wild word in the program-name position (the head, or right after asudo/env-style one-level prefix) now counts as being every tool this checks for; a wild word elsewhere (an ordinary argument, a path) is unaffected.find ~/.namzu -exec rm {} \;-style commands are unaffected: the fix is scoped to the program-name position, not to "any wild word anywhere." - —Security fix, live tool authorization behaviour changes (
packages/sdk/src/runtime/query/): the same review found that adenyrule written against a command's real name ("git push*": deny) never matches one produced by a substitution or a variable ($(echo git) push,`echo rm` -rf x,$X push, or a variable in the program's path,"$HOME"/bin/tool), since the name never appears as such anywhere in the call's text — for the scheduled-run floor's script check (verifyScheduledScript, which now refuses such a command outright, naming it, the same posture as an opaque script) and, pre-existing and more broadly, for the livebashtool's own permission rules. An operator now sees more review prompts: a bash call whose lexed command's own program-name word expands is escalated (ToolCallSummary.escalation.unknownProgram, new field) the same unconditional way a sandbox escape already is — never approved by an allow rule, a remembered grant,accept-editsorauto/an unattended turn, asked about in every mode that gets that far (naming why: "the program this runs is decided at runtime:<text>"), and refused outright with no prompt (UNKNOWN_PROGRAM_UNATTENDED_REFUSAL, new export) — there is no unattended opt-in for this the wayunattendedSandboxEscape: 'allow'is for a sandbox escape. Adenyrule that matches some other way (the tool's own name, an unrelated word) still refuses the call outright, unaffected. Argument-level expansion alone does not trigger this — only the program-name word — so an ordinaryecho $(date)is unaffected. New audit actionsunknown_program(approved/refused). Seedocs/sdk/escalations.md. - —Security fix, follow-up to both of the above — one extra word defeated every one of them: a third review found that all three consumers (the escalation, the scheduled-run floor,
verifyScheduledScript) only ever looked at a command's literal head word, soenv $(echo git) push,command $(echo git) push,exec $(echo git) push, and a re-exec wrapper with a mandatory argument of its own before the program (timeout 5 …,stdbuf -oL …,chrt 0 …,env VAR=value …) all ran, or passed the script check, unverified — the floor's one-hopREEXEC_PREFIXguess put the program at the wrong word entirely for these, andexec/command/taskset/time/builtin/xargswere not in it at all.eval/source/.were not read as unverifiable either, thoughcommand-line.tsalready treats them as opaque fordeny/allowrules. New SDK module,packages/sdk/src/authorization/program.ts, exported from@namzu/sdk:programPositions(command, dialect)is the one place "where does this command actually exec a program" now lives — it unwraps a chain of re-exec wrappers (sudo,doas,pkexec,env,nice,ionice,nohup,setsid,timeout,stdbuf,chrt,taskset,time,command,builtin,exec,xargs) with each one's own real option grammar, failing closed on an option it does not recognise, and reads eachfind -exec-family clause as its own position.source path/. pathandeval word…are read the same way a nestedbash -c '<literal>'payload already is: a LITERAL path is known, exactly asbash pathis (nothing inspects either one's contents), and a LITERALevalpayload is joined and lexed as a command line of its own, with every position inside it folded in (soeval 'env $(echo git) push'is still unknown, transitively); an expanding path/argument word, or a payload that does not read cleanly, is unknown.hasPoisoningPrefix/poisonsLaterCommands/poisonedProgramPosition/resolveScriptPrograms/DYNAMIC_RESOLUTION_VARIABLEScover the other half: a command-prefix orexport/bare assignment toPATH,LD_PRELOAD,LD_LIBRARY_PATH,BASH_ENV,ENVorIFSmakes a LATER literal program name unverifiable too. Behaviour change:unknownProgram's text is now the command plus why its program's position could not be resolved, not a bare word — a host that pattern-matches the exact string should match on substring/prefix, not equality.ToolCallEscalation.unknownProgram's doc comment,escalationReason's wording, and the floor's ownheads/REEXEC_PREFIXmechanism (removed, replaced byprogramPositions) all changed accordingly.packages/sdk/src/authorization/__tests__/program.test.ts(new). Seedocs/sdk/escalations.md,docs/sdk/command-lines.md,docs/cli/scheduled-tasks.md. - —Security fix, two CRITICAL bugs from a final review of
program.ts, plus wrapper coverage: (1)sudo's own case never skipped aNAME=valuepair the wayenv's already did, sosudo VAR=value $(echo ls)reported the ASSIGNMENT WORD as the program and never examined the real, wild one after it; bothsudo's andenv's assignment-skipping now also feed a poisoning name (PATH=,LD_PRELOAD=, …) into the poisoning check instead of silently passing it through. (2)executor.ts'sunknownProgramOfignored the reading's ownopaque/!completeflag, sobash -c "$X"— whose payload the lexer cannot follow at all — was never escalated; a new exported function,unknownProgramInLine(value, dialect), checks that first, andexecutor.tsnow delegates to it rather than re-implementing the walk. New wrapper coverage:busybox/toybox(transparent applet dispatch);declare -x/typeset -x(any option cluster with it, never+x) now poison likeexport, andexport -ncorrectly does not;su -c/runuser -c/script -c/flock -cread a literal payload recursively like a nestedbash -c '<literal>'(su/runuserwith no-care unknown: an interactive login shell);flock's positional form;unshare,nsenter,chroot(unknown with no command),setpriv,prlimit,numactl;watch(default payload joined and lexed likeeval's,-xexecs the argv directly);sudo -i/-s/-e/--login/--shell/--editand baresudoeditare explicit unknowns.ssh host cmdis deliberately not modelled (the command is remote);docs/sdk/escalations.mdnow documents this residual boundary — the full modelled-wrapper list, and that an unmodelled one is treated as the program itself, withdenyrules, the floor and ordinary review still applying to it.packages/sdk/src/authorization/program.ts,runtime/query/executor.ts,packages/sdk/src/authorization/__tests__/program.test.ts.
@namzu/cli(major)- —A pure
scriptjob now creates and runs without a configured provider or--model. New script jobs omit the unusedmodelfield; older confirmed script jobs that already store a real model still read with their confirmation digest intact and keep it on a script edit. Explicit--model,--effort, prompt, agent budget, provider wait, approval TTL, session retention and browser grant inputs are refused for a pure script instead of silently ignored; older stored fields remain readable but inert.agentandscript+agentstill require a model; converting a model-free script to either kind requires one. Text previews, the SDKScheduleJobPreview.budget, andschedule showreport zero model tokens and the script timeout for pure scripts. The schedule tool's success message reports pure script history without claiming a session. - —Breaking, on disk: the job/run-result/history file format moves to
v: 2for a job that actually usesrunKind/script, acheck-failedstatus, orgateResult/scriptOutput. An older namzu that reads such a file refuses it loudly ("was written by a newer namzu...") rather than misreading a script job as a malformed agent job. A plainagentjob staysv: 1, byte-for-byte as before, and every existing job keeps reading and confirming exactly as it did — downgrade only affects a job you have actually converted toscript/script+agent. - —Fix: a job file an older namzu (or a hand edit) left in a format the current one refuses to fully parse was previously invisible to
createJob's name-uniqueness check and tofindJob(by name or id prefix) — a name collision with it went undetected, and looking it up said "No scheduled job is named…" instead of the real reason. Both now read a refused file'sid/nameleniently, off the raw JSON, without validating anything else about it: a name collision with such a file is now refused, naming the file and the reason; looking it up by its own name or a matching id prefix now reports the real reason (e.g. "written by a newer namzu…"); a name or id prefix matching more than one job — readable, unreadable, or a mix — is an error listing every match and how to address each.schedule listsaysN job files could not be readinstead ofNo scheduled jobswhen every job file is such a file. - —New
schedule add/editflags:--kind agent|script|script+agent,--script/--script-file,--shell bash|sh,--script-timeout. Ascriptjob needs--shelland a non-empty script and has no--prompt;--unmatched parkis refused for a purescriptjob (nothing can wait for the operator mid-script);--execution sandboxis refused forscript/script+agentin this release;script/script+agentjob creation is refused outright on native (non-WSL) Windows, where the only available shell reading is a loosecmd.exeapproximation the floor cannot fully verify. - —Fix:
--shell shpreviously only labeled a script for verification while fire always used the live bash tool's automatically chosen shell. On a bash host, a newly confirmedshjob was guaranteed to stop at first fire. A script now executes under its chosen installedbashorsh; creation and every confirmation refuse an absent interpreter, and fire blocks if it has since disappeared. Native Windows remains refused. - —
ScheduleRunStatusgainscheck-failed(a script or wake-gate malfunctioned: non-zero exit, its own timeout, or stdout that is not the wake-gate's JSON contract) — CLI-internal, not part of the SDK's public surface. It joins the failure bucket for the failure streak and automatic pause, and gets its own notification andnoticeTextcase. - —
schedule show/history --jsongaingateResult({ wake, contextChars }) andscriptOutput({ stdout, stderr }, capped with an explicit truncation marker) on a firedscript/script+agentrun;show's text view gains a "Script"/"Wake-gate script" section beside "Prompt". - —
schedule list(text and--json) now shows each job's kind:[script, 0 tokens]and[script+agent]in the text view,kind(absent foragent) in--json. The TUI's/schedulelist marks the same two kinds. Found missing in a UX review — nothing distinguished a zero-tokenscriptjob from anagentjob in either listing. - —Security fix: a
script/script+agentrun's own output — a wake-gate parse failure's quoted stdout, a script-check refusal's quoted command, acheck-failednotification's reason,scriptSummaryOf's extracted summary line, andschedule show/history's text-viewreason/summary— is untrusted runtime text, not anything an operator confirmed, and is now run through the samesanitizeLine()a model's own answer already is before it reaches a terminal or a desktop notification, so it cannot plant control characters, terminal escape sequences or invisible/bidi characters on the operator's screen.--jsonoutput is unaffected and stays raw. - —Security fix: CLI and TUI schedule add/edit/confirm/list/show text views now show terminal controls and bidi characters in script text, prompts, changed lines and folder paths as visible code point escapes, so a script cannot erase or reorder its own confirmation. The model's schedule-tool confirmation uses the same projection. Persisted jobs and JSON output preserve the original source text.
- —Security/UX fix: the TUI's
scheduletool host now acts on anupdate'srunKind/script: previously it always carried a job's existing kind and script forward unchanged, silently, whatever the model asked to change. Moving tokind: 'agent'drops the script (an agent job cannot carry one); a change that toucheskindorscriptshows up in "Changed since it was last confirmed" the same as every other field, alongside the new script's full text in its own section (packages/cli/src/tui/schedule/tool-host.tsupdateRequest,packages/cli/src/schedule/changes.tsconfirmationView). - —Security fix:
namzu schedule __firenow refuses ascript/script+agentrun outright on native (non-WSL) Windows, the same way job creation already does. Job creation alone was not enough: a job confirmed on WSL or Linux, whose daemon later finds itself running on native Windows (a movedNAMZU_HOME, a machine re-imaged from WSL to a native install), previously ran anyway whenever the job's confirmed shell dialect happened to still read assh— the label native Windows'scmd.exeapproximation and POSIX sh both report, so the existing dialect-mismatch check alone could not tell them apart. - —A
script/script+agentscript (and the scheduled-run floor generally, so a livebashcall benefits too) may now use$(…)/backtick command substitution: it is checked the same as the rest of the script instead of refusing the whole thing outright (see the SDK'slexShellCommandLineentry above). Theschedule-taskskill's guidance on this is updated to match; the two narrow shapes still refused (a substitution beside brace expansion, or unquoted as a</>redirection target) are named there with why.
Upgrade step: nothing changes for an existing
agentjob. Adopting ascript/script+agentjob on aNAMZU_HOMEyou may downgrade the CLI on later means that job's files become unreadable by the older version until you upgrade again; keep that in mind before converting a job you might need to roll back. - —Breaking type correction:
- —
567ada8:
@namzu/cli: the session's tool roster is now composed from namedToolsets (@namzu/sdk's toolsets, plan.md v3 §8) instead of aToolRegistry. This is internal composition, not a config or CLI-flag change — nothing innamzu.config.jsonor the command line changes — but it is a breaking change for anything that imported@namzu/cli's internals directly:Removed / changed Replace with createCliPluginRuntime(config, tools: ToolRegistry, cwd, hooks?, skillTool?)createCliPluginRuntime(config, cwd, hooks?)— it no longer takes or mutates a tool registry. Readruntime.skills.sizeand foldruntime.manager.toolsetsinto your own toolsets list; theskilltool is your own toolset now, not something this runtime registers.SubagentRuntimeOptions.buildTools: () => ToolRegistryContractbuildTools: () => readonly Toolset[].SubagentRuntimeOptions.configureWebSearch?: (provider, model, tools: ToolRegistryContract) => WebSearchConfig | undefinedconfigureWebSearch?: (provider, model, toolsets: readonly Toolset[]) => WebSearchConfig | undefined— a pure decision now; it no longer removesweb_searchitself, so a caller building the child's final toolsets filters it out when this returns truthy (filtered(ts, (t) => t.name !== 'web_search')).McpConnection.tools: readonly ToolDefinition[](still present)McpConnection.toolsets: readonly Toolset[], two per connected server (main tools and deferred resources, both kindmcp_server, idmcp:<server>) — prefer these;.toolsis now just the flattened union across all of them.Additive:
AgentSessiongained apresenter: ToolPresenterfield — the session's real tool-call/result presenter, for a host that renders events outsidesend()'s own stream. The ACP bridge (namzu acp) now uses it instead of a presenter built over a permanently empty registry, so tool-specificpresentCall/presentResultviews (a richer diff, a custom label) render over ACP for the first time; every ACP tool view had silently fallen back to the generic label before.Behaviour: because tool availability is now derived from the turn's own revealed-tool history (
@namzu/sdk'sToolManager) rather than a per-send registry fork, a toolsearch_toolsreveals in one send now stays active in a later send of the same session, and afternamzu resume— it no longer resets every send.@namzu/sdk: twocombineToolsetsusage bugs, found while wiring the CLI onto toolsets, are fixed —query()'s own generated tools andSupervisorAgent's coordinator tools are now two separate array entries (an eager one and a deferred one) rather than onecombineToolsets'd together, soruntimeToolOverrides: { name: 'deferred' }(task tools, coordinator tools) is honoured again instead of silently falling back to'active'for both halves.query()also no longer adds its ownsearch_toolswhen a caller'stoolsetsalready contribute a tool under that exact name (a connector genuinely namedsearch_tools), matching the pre-toolsets registry's behaviour and avoiding a spuriousToolsetConflictError. Pure bug fixes, no API change.
Minor Changes
- —
567ada8:
mcpServersentries innamzu.config.jsonmay now reference the operator's own environment from inside anenvorheadersvalue with a bare${VAR_NAME}(identifier characters only — no${VAR:-default}fallback). This is narrower than the interpolation some other MCP clients' configs use: onlyenvandheadersvalues are expanded (nevercommand,args,urlorcwd), and a reference to a variable that is not set fails that one server with a named reason instead of substituting an empty string.inheritEnvremains the primary way to grant a named variable to a stdio child under its own name;${VAR_NAME}is for the value itself, and is the only secret-safe optionheadershas ever had.Purely additive: a config with no
${...}in itsenv/headersvalues behaves exactly as before. A config that happened to contain a literal${SOMENAME}string matching a variable name that is set in the operator's environment now has that string substituted rather than passed through literally. - —
567ada8: Plugin manifests can declare
instructions. An enabled plugin contributes the text as labelled, untrusted request context; disabling it revokes the contribution, including for an already-running turn. SDK hosts can also calldefinePlugin({ name, tools, hooks, instructions, mcpServers })andPluginLifecycleManager.installDefined(plugin)to install a host-authored plugin without a manifest file or dynamic module import.
Patch Changes
- —
567ada8: Fixed:
namzu acp's tool-call/result presenter (added in this release to fix the previously-always-empty ACP presenter) delegated through one connection-global "active session" slot. Two ACP sessions with prompts in flight at the same time could have session A's tool-call/result rendered with session B's tool-specific view once B's prompt started — worse than the pre-fix behaviour (always generic, but never wrong) for exactly the concurrent caseacp-session-isolation.test.tsotherwise covers.The presenter now tracks which session's event it is presenting for only the synchronous span of that one event — bracketed around each
record.route(event)call rather than held for a whole turn — so a second session's prompt starting, or finishing, while the first is still streaming can no longer be read as "active" while the first session's own event is being presented. - —
567ada8: The SDK now accepts application-defined agent type strings and provides
QueryAgentas the generic managed query adapter.defineAgentgives itsruncallback a required fourthAbortSignalparameter and creates fresh instances for delegated turns. Code that invokes aDefineAgentOptions.runcallback directly must supply that fourth argument; existing three-argument callback implementations remain assignable.ReactiveAgentand the supervisor, pipeline and router exports remain available as deprecated compatibility names. CLI delegated turns use a CLI-ownedNamzuCliAgentwhile keeping their existing configuration behavior. - —
567ada8: Authorization rules can now match the owning toolset's source id with
{ type: 'by_source', sources: ['mcp:github'], decision: 'review' }. This lets a host ask, allow or deny every tool from one or more MCP servers without listing names individually. A rule with no matching host-supplied source leaves the call to later rules.The CLI accepts
[permissions.sources]with source ids or globs mapped toallow,askordeny, and its permissions display shows those rules and their decisions. - —
567ada8: Releases live toolset change listeners when a CLI session or turn ends, including a send with extra per-turn tools. Closing the session keeps its plugin and MCP connection cleanup while dropping the host-side manager subscriptions.
- —
567ada8:
CommandNameCollisionError(tui/slashCommands.ts, thrown when a kernel command and a CLI-local one claim the same name) now extends@namzu/sdk'sRegistryCollisionError, matching every SDK*CollisionErrorafter its registry-collision convergence. Name, message and behavior are unchanged; a catch block namingCommandNameCollisionErrorstill works, and one that wants "some registry collided" without naming every class can now catchRegistryCollisionErrorinstead. See docs/sdk/registries.md. - —
567ada8: MCP tool definitions that change after initial admission keep serving their earlier definition across turns; new and removed names still update.
/mcpandexec --jsonnow report server discovery changes and current policy refusals, including changed definitions held for review. - —
567ada8: Former MCP permission-name warnings now use the CLI's structured logger instead of writing plain text to stderr. Consumers of
exec --jsonstderr can continue parsing log records when a legacy permission rule is configured. Plugin MCP disconnect warnings use a fixed message and put the operation in a log attribute. - —
567ada8: Shipped prose no longer names other named products.
matchesToolSelector's doc comment (packages/sdk/src/tools/roster.ts) and Tool metadata and selectors describe its synchronous-only design against "some other agent frameworks' equivalent selector" instead of one by name; Tool servers describes the wider${VAR}/${VAR:-default}interpolation some MCP clients accept as "several desktop and editor MCP clients" instead of naming two. Wording only: no type, default or behaviour changed. - —
567ada8: Invalid JavaScript plugin tools now fail when a plugin is enabled, with the plugin and module named, if they omit a usable
inputSchema. Before, a deferred plugin tool could load successfully and then crash the turn with a Zod_deferror aftersearch_toolsrevealed it. Supply a ZodinputSchema, or a compatible parser and explicitmodelInputSchema. - —
567ada8: Fixed: under
toolLoading: 'deferred',search_toolswas refused by review in every mode butauto—planandstrictrefused it outright, and the interactive default (prompt) put up an approval dialog for it on every discovery call, where before the toolsets migration it ran silently. The session's own review-exemption manager is built fromsessionToolsetsalone and never learns aboutsearch_tools(mounted internally, per turn, byquery()), soreviewExemptionFor's lookup resolved it as an unknown tool and treated it as never exempt.reviewExemptionFornow falls back to the SDK's ownsearch_toolsdefinition for a name the session's manager does not carry, read exactly the way the kernel reads a known tool (isTrustedReadOnly), so the exemption holds regardless of whether this turn'squery()actually mounts it. This does not addsearch_toolsto/toolsor/permissions(promptExemptToolNames), which still read the session's roster bare and stay fixed at boot, astools-and-permissions-agree.test.tsrequires.This is also what makes the earlier claim in this same release — that a tool
search_toolsreveals in one send stays active in a later send — true forpermissionMode: 'plan'too; it previously held only forauto, since a blockedsearch_toolscall never revealed anything to persist. - —
567ada8: Restores file-defined sub-agent shadowing: a project agent file named
exploreorgeneral-purposeagain replaces the matching built-in, using the newAgentRegistry.replaceinstead ofregisternow that registries throw on a duplicate id by default (@namzu/sdk's registry-collision convergence). No change from the last published@namzu/clibehavior — this restores parity with a regression introduced earlier in this same line of work, before it ever shipped. - —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [fbeac55]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —
Updated dependencies [567ada8]
- —@namzu/sdk@48.0.0
- —@namzu/browser@0.1.1
- —@namzu/computer-use@2.0.2
- —@namzu/anthropic@6.2.2
- —@namzu/deepseek@2.0.2
- —@namzu/openai@4.1.3
- —@namzu/openrouter@3.0.3
- —@namzu/zen@2.5.2
- —@namzu/ollama@2.2.5
- —
Major Changes
- —
82769f1: The interactive CLI now asks once per session before the model first sees your screen, and
strictmode no longer lets a screenshot run on its own — that second part is why this is a major release for@namzu/cli. Inprompt,accept-editsandplanmode the firstcomputer_usecall that would send the screen to the provider — a screenshot, a zoom, the list of open windows, a window's controls, or an action that returns a screenshot — opens a "Share your screen" box naming the provider; after a yes, later screenshots in that session run without asking, and clicks, typing andui_actare still reviewed as before. A mode switch keeps the answer; a new session (/new,/clear,/model) asks again.autonever asks.strictnow refuses a screenshot unless a rule allows it: to keep screenshots running instrict, addpermissions: { computer_use: 'allow' }(which also allows clicks) or a narrower rule for the actions you want.Approving a
ui_actcall shows the control by name (Press Button "Beş" (e30)), andui_snapshotas the window it reads.@namzu/sdk- —
ToolDefinition.capturesScreen?(input)anddefineTool({ capturesScreen })declare which calls send the operator's screen to the model provider.computer_usedeclares its observations and every action followed by a screenshot. - —Breaking: the authorization gate's
allow_read_onlyrule (allowReadOnlyTools: true, in every shipped preset) no longer allows a call that declarescapturesScreen; it goes to the review policy instead. WithcreateReviewHandlerand noscreenConsentnothing changes (a screenshot is approved as a read); a customResumeHandlernow seescomputer_usescreenshots, zooms, window lists and UI snapshots. To keep them out of review, add an explicit rule such as{ type: 'allow_by_name', toolNames: ['computer_use'] }(which allows clicks too). - —
createReviewHandler/createReviewPolicytakescreenConsent: { sessions: Set<string> }and an optionalcapturesScreen(name, input)predicate (default: the tool's declaration fromregistry). With them, the first such batch in a session is put topromptwith the newToolReviewRequest.screenConsent: true;strictrefuses it unless a rule allowed it,autodoes not ask, and without apromptit is refused with the newSCREEN_CONSENT_UNATTENDED_REFUSAL. A no isSCREEN_CONSENT_DECLINED_FEEDBACK. New typeScreenConsentRecord. WithoutscreenConsentnothing changes.
- —
Minor Changes
- —
172fd9e: New
open_urltool: in the interactive terminal andnamzu exec, the model can open an http(s) page in your default browser (under WSL, the Windows browser) instead of guessing a shell command. It is anetworktool, sopromptmode asks before it runs. Add a[permissions]rule foropen_urlto allow or deny it.exec --json,drain, ACP, scheduled runs and sub-agents do not have it. Each request in those two surfaces now carries about 120 more tokens of tool schema. - —
28102e1: The model can change a scheduled job instead of deleting and recreating it. The
scheduletool has a new action,update:jobnames the job, and only the fields the model sets change (prompt,when,folder,tz,budget, orpermissionsas the whole new set, under the same limits ascreate). Asked whether it could change a job, a model with no such action deleted the job and created it again, and the job's history was lost.In the TUI the operator confirms every change on one screen: what changes first (the
-/+linesnamzu schedule editshows), aTHE PERMISSIONS CHANGEwarning when a run's rules,unmatched, execution or browser grant change, then the job as it will run.Cancelis the default.Savewrites the same job — its id, creation time and history kept — with the operator's new confirmation, keeps a paused job paused, and recordsedited by toolwith the changes in its history. A change is refused if the job was changed while the operator was being asked. Likecreate,updateis not preceded by the ordinary permission review inprompt,accept-editsandauto.SDK:
ScheduleToolHostgains three optional methods,previewUpdate(job, changes),confirmUpdate(request, signal?)andupdate(preview), and new typesScheduleJobChanges,ScheduleJobUpdateProposalandScheduleUpdateRequest. A host without them keeps working: the tool refusesupdatethere and tells the model not to delete and recreate the job. - —
28102e1: A scheduled run that completed with refused tool calls now says so. A job whose only command was refused on every run was recorded
completedeach time, with afinishednotification and nothing else to go on.A run now records
refusedCalls(calls that never ran: a permission rule, the scheduled-run floor,unmatched: deny, a tool its permissions withhold) andfailedCalls(calls that ran and returned an error), each{ count, first: { tool, reason } }, in its result file and history record, and as counts in the job state'slastRun.namzu schedule listshowslast completed (1 call refused),showandhistoryprint the first reason under the run,run-nowprints it, and so do/scheduleand the model'sscheduletoollist. The finished notification readsdone at …, but 1 call was refused (bash); namzu schedule show <job> says why; the reason itself appears in the notification only for a job created with--notify-summary, because it can quote the command the model wrote.The run's status is unchanged:
completedstill means the turn ended normally, so failure counting, automatic pausing and which notification is sent behave as before. The new fields are additions to the--jsonoutput oflist,showandhistory.
Patch Changes
- —
82769f1: Computer use no longer runs blind on a provider that cannot show the model an image in a tool result (OpenAI via API key, Bedrock, OpenRouter, LM Studio, Ollama, the generic HTTP driver). The
computer_usetool is still listed, says why it cannot be used, and refuses every call; the desktop is never touched, and the session notices sayComputer use is unavailable in this session: …. Use Anthropic, Codex or Google for computer use.Approving a
computer_usecall now shows each desktop action on its own line, in the order it will run, with any text to be typed shown in full. - —
28102e1: A scheduled run can no longer reach the scheduler through a shell the command-line reader does not follow.
powershell -c 'namzu schedule stop',pwsh -command '…',fish -c '…',tcsh -c '…'andbash.exe -c '…'were allowed whatever their text held: the scheduled-run floor took any shell at the head of a command with a-coption as read, but only the payloads ofsh,bash,dash,zsh,ksh,ashandmksh(andbusyboxrunning one) are. Such text now goes through the floor's tripwire like any other text a program runs as code. A job whose commands use those shells without naming the scheduler,NAMZU_HOMEor the browser profiles runs as before.SDK: new exports
nestedShellCommand(words),NESTED_SHELLSand the typeNestedShellCommand.nestedShellCommandsays, for one simple command's words (without its leading assignments), which-cpayloadlexShellCommandLinereads as a command line of its own, why it made the line opaque instead, ornullwhen it reads none. The lexer uses it itself, so its reading is unchanged. - —
28102e1: A scheduled run may now run a command whose code merely mentions namzu.
powershell.exe -NoProfile -Command "[System.Windows.MessageBox]::Show('Namzu: scheduled job running','Namzu')"was refused by the scheduled-run floor, because text passed to PowerShell,cmd, Python, Node or a shell reading its input was refused whenever it containednamzuorschedul. Such text is now refused only when it holds something that can reach the scheduler orNAMZU_HOME: aschedulesubcommand other thanlist,show,status,historyorlogswith the CLI or an expansion in reach, the service's name (namzu-scheduler…,com.namzu.…), a service tool with a namzu name or a*,pkill/killall,NAMZU_HOMEby name,.namzuas a path segment, a path into either protected folder, orLOCALAPPDATAwith anamzusegment. A job that was refused for naming the product runs; nothing that reached the scheduler before is let through (checked against bash on 35 159 generated lines, and on 1 189 labelled lines of PowerShell,cmd, Python, Node andshcode).The refusal now says which rule matched and where (
the argument `~/.namzu/x` names NAMZU_HOME (/home/you/.namzu),… it holds `schedule stop` (a `namzu schedule` subcommand other than …), in the argument `namzu schedule stop`) instead of listing everything the floor protects.SDK: a
predicateauthorization rule may carrydescribe(call), asked afterdecidereturned a decision; its answer is the gate's reason for that call instead of the rule's fixeddescription(anull, empty or thrown answer keepsdescription).describeRule(rule, call?)takes the call as an optional second argument. Both are additions; existing rules and callers are unchanged. - —
28102e1: In the TUI, the model's
scheduletool now lists every scheduled job, not only those of the session's folder. A model that created a job in a folder below the session's and then calledlistwas told "No scheduled jobs." whilenamzu schedule listshowed the job, because the TUI's host filtered by folder unless the model passedallFolders: true, which models do not. Jobs of the session's own folder are marked(this folder)and are still the only ones whose prompt the model sees.SDK:
ScheduleJobSummarygains an optionalinSessionFolder, which thescheduletool prints as(this folder)on the job'slistline. A host may use it to list every job regardless ofallFolders. The tool still askshost.list({ allFolders: false })unless the model sets it, so a host that filters by folder behaves as before. - —
28102e1: A skill the model loads now names the directory its files are in (#536), so a skill whose instructions say
scripts/…orreferences/…can be followed. On the host it is the directory the skill was read from. Withsandbox.enabled, a skill under the working directory or an added directory gets the same path, which the sandbox mounts; every other skill (the user and shared-user tiers, the built-ins,.agents/skillsabove the working directory, user plugins, and every skill under anephemeralworkspace) is reported as not reachable instead of being given a host path the sandbox refuses. Nothing to do on upgrade. - —
28102e1:
namzu schedule statuson Windows and WSL now says what the Task Scheduler's last result means, keeping the number:last result: running (267009, 0x41301)instead oflast result 267009. The result that appears every five minutes while the scheduler runs,-2147020576(0x800710E0), readsan instance was already running, so a new one was not started; expected, since the task checks every five minutes, so it is not mistaken for a failure. Task Scheduler's own codes, the common Windows errors and the scheduler's own exit codes are named; any other result shows its number and its hexadecimal form. - —
172fd9e: Under WSL, the Claude, Codex, Gemini and OpenCode sessions you are signed in to on Windows are now found even when
/etc/wsl.confmoves the Windows drives ([automount] root). The paired Windows home used to be looked up only under/mnt/c. A scheduled run'sPATHnow also drops the Windows drive entries under that root, instead of only those under/mnt/. On the default/mnt/nothing changes. - —
172fd9e: Under WSL, several things now look for Windows programs under the drive mount root set in
/etc/wsl.conf([automount] root) rather than always under/mnt/c: the scheduler service's programs (schtasks.exe,cmd.exe,powershell.exe, …) and the directory they start in, Windows notifications, and the paths the model is told about. On a distro that moved the root,namzu schedule installused to refuse and notifications never appeared. On the default/mnt/nothing changes. A service installed while the root was elsewhere keeps the program paths recorded in its manifest. If you have since moved the root, reinstall it withnamzu schedule install. - —
172fd9e: Under WSL,
namzu loginnow opens the sign-in page in the Windows browser. It launches Windows PowerShell by absolute path, under the drive mount root set in/etc/wsl.conf([automount] root,/mnt/by default), and the address is passed as data, never as script text. It used to callxdg-open, which under WSL usually opened nothing, so you had to copy the URL. When interop is off or PowerShell is missing, it still falls back toxdg-open. Nothing to change on your side. - —
Updated dependencies [28102e1]
- —
Updated dependencies [28102e1]
- —
Updated dependencies [82769f1]
- —
Updated dependencies [82769f1]
- —
Updated dependencies [82769f1]
- —
Updated dependencies [82769f1]
- —
Updated dependencies [82769f1]
- —
Updated dependencies [82769f1]
- —
Updated dependencies [28102e1]
- —
Updated dependencies [28102e1]
- —
Updated dependencies [28102e1]
- —
Updated dependencies [28102e1]
- —
Updated dependencies [28102e1]
- —
Updated dependencies [28102e1]
- —
Updated dependencies [28102e1]
- —
Updated dependencies [82769f1]
- —
Updated dependencies [28102e1]
- —@namzu/anthropic@6.2.0
- —@namzu/sdk@46.0.0
- —@namzu/openai@4.1.1
- —@namzu/computer-use@2.0.0
- —@namzu/browser@0.1.0
- —@namzu/ollama@2.2.4
- —@namzu/openrouter@3.0.1
- —
Minor Changes
- —
2b96136: The interactive terminal can drive a web browser.
@namzu/browseris now a dependency, and the TUI mounts thebrowserandbrowser_acttools on a namzu-owned profile; in WSL they drive the Windows Chrome. Nothing launches until the model's first browser call.namzu exec,exec --json,drain,acpand the resident step do not get the tools.What changes for you:
- —Browser calls are reviewed by default. With no config, opening any site and every action on a page is reviewed (
"*": ask); looking at the page the browser holds is not. Setbrowser.sitesto allow sites (read,act) or refuse them (deny), orbrowser.enabled: falseto turn the tools off. - —New config key
browser(enabled,defaultProfile,engine,headless,sites,keepOpen). An unreadable site key or level stops namzu from starting and names the key. The key is merged across files per site; a deny in any file holds. A project file that setsbrowser.defaultProfileis refused, and namzu will not start in that folder until the key is removed. - —Site rules come before your
[permissions]table for the browser tools. A tabledenyforbrowserorbrowser_actstill wins; a tablealloworaskfor them applies only toback,forwardandreload. - —New commands:
namzu browser login <profile> [url],list,status,install,remove; the/browserslash command (status,profile <name>). - —
namzu doctorreportsbrowser.installedandbrowser.engine. The boot capability line gainsbrowser yes|no. - —The review screen names the site rule, profile and engine for a browser call. A turn paused because a page needs you (sign-in, CAPTCHA) says where to do it and how to continue.
- —New exports:
browserInstalledCheck,browserEngineCheck;NAMZU_OPTIONAL_CAPABILITIESincludes@namzu/browser.
See docs/cli/browser.md.
- —Browser calls are reviewed by default. With no config, opening any site and every action on a page is reviewed (
- —
2b96136: The CLI ships three built-in skills and a way to make your own from the TUI.
- —
skill-creator(model and operator),browser-automation(model only, offered only when thebrowsertool is present) andschedule-task(offered where thescheduletool is: the TUI). Every session now carries theskilltool and listsskill-creatorin its skills manifest. To go back to no built-ins, setskills.builtin: false; to drop one, name it inskills.disabled; a skill of the same name in~/.namzu/skills,~/.agents/skillsor the project replaces it. - —
/skills new [what it should do]starts an interview with the model, which drafts aSKILL.mdand proposes it through the newsave_skilltool. Nothing is written until you choose Save to user (~/.namzu/skills), Save to project (./.namzu/skills) or Cancel on a screen that shows the whole file with invisible characters revealed and names any skill it replaces. The screen asks in every permission mode,autoincluded;planandstrictrefuse the tool.save_skillexists only in the interactive TUI, never inexec,drain, a scheduled run or a sub-agent./skills newis now a subcommand, so a skill namednewis activated from the/skillspicker rather than by/skills new. - —The skill tiers are read again at the start of every turn, so a skill added while a session runs is offered from the next turn.
- —
- —
2b96136: Scheduled jobs can drive the browser, on the sites you list and no others.
- —New flags on
namzu schedule addandedit:--browser <profile>,--browser-site <site>=read|ask|act(repeatable) and--browser-headed; onedit,<site>=nonetakes a site off and--no-browserremoves the grant. Sign in first withnamzu browser login <profile> <url>.*is refused: every site not listed is denied.askneeds--unmatched park. A browser grant alone (no--permissions) needs--unmatched. - —New job field
permissions.browser(profile,sites,headed), covered by the confirmation. The model'sscheduletool in the TUI can now propose it; you confirm it on screen. - —A job's
[permissions]rules may no longer namebrowserorbrowser_act; creating or editing such a job is refused with a pointer to the grant. Without a grant both tools are denied in a scheduled run, as before. - —A run with a grant checks its browser before the model: the profile exists and has its data, the browser it was signed in with can start (WSL interop for the Windows browser), a display for
--browser-headed. Otherwise it isblocked-configwith thenamzu browser logincommand. - —A run a page parked for you (a sign-in, a CAPTCHA) reads
needs you: <reason>inschedule list,show,status,/scheduleand the TUI's startup line instead of "waiting for approval".--jsongainsactiveRun.handoff(list) andawaitingApproval[].handoffandwaitingFor(status). - —The scheduled-run floor also refuses tool arguments naming the Windows browser's profile folder (
…/AppData/Local/namzu,%LOCALAPPDATA%\namzu). A site a config file'sbrowser.sitesdenies stays denied for every job. - —
DISPLAY,WAYLAND_DISPLAYandXAUTHORITYnow reach a scheduled run's environment.
See docs/cli/scheduled-tasks.md#browser-access.
- —New flags on
- —
d233f26: Add scheduled jobs: prompts that run later in a folder while namzu is closed, under a permission set you write down, run by a scheduler service.
New commands, all under
namzu schedule:add,edit,confirm,list,show,history,pause,resume,remove,run-now,prune,install,uninstall,status,start,stop,logsanddaemon.installregisters a systemd user unit, a launchd agent or a Windows scheduled task (under WSL, a task that runs the daemon throughwsl.exe). New config keyschedule(maxConcurrentRuns,notifications), read from the user and managed files only. New files underNAMZU_HOME/schedule/.namzu doctorgains ascheduler.servicecheck.namzu upgradeasks a running scheduler to restart on the new code.In the TUI:
/schedulelists jobs and what needs you and acts on them,/loopre-sends a prompt to the open conversation on an interval between turns, the model gets ascheduletool (every job it proposes is confirmed by you on a screen namzu computes) and asession_looptool, and one startup line reports scheduled work since you last looked.A job is confirmed on a terminal or in the TUI;
schedule add --yeswithout a terminal (a script, or a model's own shell call) creates it inert. This is a tripwire, not a lock against a program running as you: see "Only a terminal or the TUI confirms a job" in the docs. A scheduled run never approves a call on its own: a call its rules do not allow is refused or parks for you, and you answer it later from/resumein the job's folder, under the job's rules. Allows come only from the job; everydenyin your config files still holds.Wording only, no behaviour change:
namzu servenow says namzu has no server (it used to say no daemon, which the scheduler made untrue), andnamzu drain --helpno longer says namzu has no daemon. A script matching the oldservesentence must match the new one.See
docs/cli/scheduled-tasks.mdanddocs/cli/scheduler-service.md. - —
2b96136: The interactive terminal now proposes saving a multi-step task as a skill. After a turn that answered with at least six successful tool calls across two or more tools (one of them changing something), it prints one dim line under the reply:
✻ That took 9 steps across 4 tools. Save it as a reusable skill? /skills save [name] · /skills save off to stop suggesting. It appears at most once per conversation, takes no keys and costs no model call; nothing is saved unless you type/skills save, which drafts the skill in the same conversation and saves it only from the confirmation screen.To turn it off, type
/skills save off(it writesskills.suggest: falseto~/.namzu/config.yaml) or set that key yourself;/skills save onrestores it. It also stops by itself after three proposals in a row go unused.skills.suggestMinToolCallschanges the threshold.namzu exec,drain, ACP, scheduled runs and sub-agents never show it. - —
2b96136:
SKILL.mdskills now reach the model on their own. Every session lists the usable skills in the prompt's skills manifest and mounts theskilltool, which the model calls to load one when a task matches its description; until now only plugin skills did, and a file skill needed/skills <name>orexec --skills. Skills are read from six tiers, the later shadowing the earlier: built-in (skills/in this package),~/.agents/skills,~/.namzu/skills,./skills,.agents/skillsfrom the checkout root down to the working directory, and./.namzu/skills. The manifest is capped at 2% of the context window or 4 KB, whichever is smaller; skills that do not fit are named in one line and stay loadable.What may change for you:
- —A model working in a folder with skills now sees them and may load one. To keep a skill away from the model, add
disable-model-invocation: true(orinvocation: operator) to its frontmatter, or name it in the newskills.disabledconfig list;skills.builtin: falseleaves the built-in tier out. - —A skill whose
metadata.namzu-requires-toolsnames a tool the session lacks is not offered. - —
namzu skills-jsoncan now report"source": "system"for a built-in skill, and leaves disabled skills out. A host that switches onsourceshould accept the new value. - —
namzu skillsand/skills listshow each skill's directory tier and the files it shadows;namzu --format json skillsitems gaintier, andshadows,disabled,invocationandrequiresToolswhen they apply.
- —A model working in a folder with skills now sees them and may load one. To keep a skill away from the model, add
- —
2b96136: Handles a tool's request for a person (
ToolResult.handoff, see@namzu/sdk). ThepausedAgentEventgainshandoff. The terminal shows the reason withpress Enter to continue · Esc to stop: Enter resumes the turn and Esc abandons it.namzu execprintsTurn paused — needs you: <reason>. A scheduled run paused this way recordsawaiting-approvalwith the reason (reasonand the newhandoff.reasonin its run result), and its notification saysneeds you: <reason>.namzu resume <session-id>and/resumeoffer Continue or Abandon for such a run instead of a permission screen. Nothing to change on your side.
Patch Changes
- —
2b96136: The review screen reads a browser call in words. It was titled
browserand asked "Do you want to run browser?" overaction: "navigate"; it now saysOpen a web page/Do you want to open this page?,Go back in the browser, orClick on https://shop.example/Do you want to do this on https://shop.example?. An address opens with its path as a person writes it (/wiki/İstanbul) and, when that differs, the address actually sent (/wiki/%C4%B0stanbul) below it; the host stays in its punycode form. - —
2b96136: A generic tool result view may now set
outcome: 'cancelled'for a call the person declined on the tool's own screen.save_skilland thescheduletool'screate,resumeanddeleteuse it (thescheduletool also setsdata.cancelledon those results), and the TUI shows the row as○ … Cancelled — nothing was savedinstead of✗ … failed: Error: The operator cancelled. The model still receives the same refusal text. - —
2b96136: A tool call a person declines without giving a reason now tells the model not to get the same content or result another way — another tool, another site or address, or a web search — unless it asks first and the person agrees. The text was "User declined to run the proposed tool(s)." (and "The user rejected this tool call." on a resumed decision); in a live session a model whose browser navigation was declined fetched the same page through web search instead.
New export:
DECLINED_TOOL_CALL_FEEDBACK, the text. A host that passes its ownfeedbackwith a refusal is unchanged. A test or host that matched the old default text must match the new one. - —
2b96136: Continuing a turn a tool paused for a person — Enter at "The browser needs you", or Continue on a parked scheduled run — now tells the model that you dealt with it and to try the step again. It used to see only the tool's "needs a person" result and end the turn, so a scheduled post you had signed in again for was reported as not possible. The Continue/Abandon card for a scheduled run names the site, the profile and the sign-in command in words.
- —
2b96136: A scheduled run the browser parked on a sign-in page says how to sign in again: its reason, and so the notification,
schedule list,show,statusand/schedule, read… is showing a sign-in page; sign in again with namzu browser login <profile> <url>. - —
2b96136:
/abandon,/resumeand Abandon on a parked scheduled run no longer print the turn's id. They sayStopped the paused turn. Your next message starts a new one in this conversation.,Continuing where it paused…andStopped this run. The job stays scheduled.Nothing else changes. - —
d233f26: The permission screen shows a shell command as it would be typed. It used to escape the command the way JSON does, so
printf '%s\n' "$out"read asprintf '%s\\n' \"$out\". A multi-line command now shows one row per line, later lines indented under the first; a carriage return and other invisible characters are spelled out as\u{....}, anddstill shows the exact input. Nothing to change on your side. - —
2b96136: A tool result whose first line is up to 300 characters long shows that line whole on its
⎿row and not again underneath. It used to be cut at 120 characters on the row and repeated in full as the first line of the output below, so a blocked browser navigation's note appeared twice. A longer first line is still shortened on the row and kept whole in the output. - —
2b96136: A parked scheduled run whose job cannot run shell commands (
bashdenied, as in theread-onlypreset) can be continued from a TUI session whatever its sandbox setting. It used to be refused unless the session's sandbox matched the job'sexecution, which for a browser job created withexecution: sandboxmeant editing your config to continue it. A job that can run commands is still continued only from a matching session. The refusal now says a run a page parked "is waiting for you" rather than "waiting for approval". - —
2b96136: Esc or Ctrl+C on the screen that saves a skill now only cancels the save. It also interrupted the turn, so the model never heard that nothing was written.
- —
2b96136: The
scheduletool's input schema now says thatbudgetlimits one run and thatmaxIterationscounts model steps, not repetitions of the job. A model proposedmaxIterations: 1for a job meant to post once per run, and every run stopped after its first model call. The CLI's confirmation of a job, on a terminal or in the TUI, warns when it allows fewer than 10 iterations. - —
2b96136:
ScheduleToolHost.create()may return an optionalnote, which thescheduletool appends to what the model is told about the new job. Existing hosts that return only{ name }are unaffected. The CLI uses it to tell the model that no scheduler is installed, so its reply no longer promises a run that cannot happen until you runnamzu schedule install. - —
2b96136: A job the model proposes in the TUI and you create now says, when no scheduler is installed, that it will not run until
namzu schedule install.namzu browser loginends by saying a scheduled job uses the profile withnamzu schedule add … --browser <profile>. - —
2b96136: Confirming an edited scheduled job now lists what changed since it was last confirmed, as
+and-lines above the question, innamzu schedule edit,namzu schedule confirmand/schedule confirm. An edit saved with--yesrecords the lines in the job's history (changeson theeditedrecord inschedule history --json) so the later confirmation can show them. - —
2b96136: The
scheduletool asks the model to leave optional fields (folder, time zone, execution, budget, a visible browser window) unset unless the user asked for them, and the TUI's confirmation of a proposed job marks every such value the model set that differs from what you would get by default, e.g.Chosen by the model, not the default: time zone America/New_York, not this machine's Europe/Istanbul. - —
2b96136: A job the model proposes, resumes or deletes in the TUI is confirmed once, on the job's own screen. The permission review no longer asks "Do you want to run schedule?" first in
prompt,accept-editsorauto.planandstrictstill refuse the call, aschedulerule ofaskordenystill applies, andpauseis still reviewed. The SDK'sscheduletool no longer declaresdeletedestructive, since the host confirms it before anything is removed; a host that relied on that flag to review deletes should add anaskrule forschedule. - —
2b96136: The
scheduletool's input schema describesbudget.tokenBudgetas what the whole run may spend, with every model call resending the prompt, and the CLI's confirmation of a job warns when it allows fewer than 50 000 tokens. A model proposed 4 000 tokens for a browser job whose runs each took about 110 000. - —
2b96136: A parked scheduled run you approve or continue from the TUI is told the current local time again, since an answer can come long after the run started, and every time line now says it is the current time already looked up, so the model does not try to run
datefor it. - —
2b96136: A scheduled run is told the time it started, in the job's time zone with its UTC offset (
It is now Wednesday, 23 September 2026 at 21:04 GMT+03:00 (Europe/Istanbul)). It had only the date; a job that wrote the time into a post, with no shell to ask, wrote the time in UTC. - —
2b96136: A scheduled run is no longer sent the tools its job can never use, which every model call used to resend. For a browser job that posts once to a site with the
read-onlypreset and--unmatched deny, a run went from about 106 000 tokens to about 78 000. A tool withheld this way is refused as an unknown tool if the model names it anyway; nothing a job's rules allow is withheld. NewAgentSessionOptions.withheldTools. - —
2b96136: The
skilltool now presents its calls asRead skill <name>(orList skills) and hides a successful result, so a host shows one row instead of the raw input and the skill's body. In the TUI, the Working row saysWaiting for youwhile the screen that saves a skill is open, instead of counting on as if the turn were working. - —
2b96136:
/skills save offand/skills save onnow warn when a project file, profile or managed config sets its ownskillsblock that would override the value you just wrote to~/.namzu/config.yaml. The warning names that file and says whatskills.suggestwill be the next time namzu starts. Addsuggestunderskillsin that file to make the change stick./skills saveand/skills newtyped while a turn is running now wait and run when that turn ends, instead of being injected into it. - —
2b96136: The confirmation of a job the model proposes is shown whole again. When the model wrote a sentence along with the
schedulecall, the confirmation was drawn below that still-open reply and, taller than the screen, lost its top lines — the job, the model, the budget and its warnings — while you were asked to create it. A tool's own screen now closes the reply first. - —
2b96136: Skills drafted by
/skills saveand/skills new, job prompts the model proposes through thescheduletool, and a scheduled run's final summary now follow the language you write in (the job prompt's language for a run), instead of defaulting to English. - —
2b96136: While a question is waiting for you during a turn — a permission review, a scheduled job's confirmation, the Continue/Abandon card — the activity row reads "Waiting for you · the question below" instead of a running
Workingtimer, and the time spent answering is not counted in the turn's elapsed time. - —
Updated dependencies [2b96136]
- —
Updated dependencies [2b96136]
- —
Updated dependencies [2b96136]
- —
Updated dependencies [2b96136]
- —
Updated dependencies [2b96136]
- —
Updated dependencies [2b96136]
- —
Updated dependencies [2b96136]
- —
Updated dependencies [2b96136]
- —
Updated dependencies [d233f26]
- —
Updated dependencies [2b96136]
- —
Updated dependencies [2b96136]
- —
Updated dependencies [2b96136]
- —
Updated dependencies [2b96136]
- —
Updated dependencies [2b96136]
- —
Updated dependencies [2b96136]
- —
Updated dependencies [d233f26]
- —
Updated dependencies [2b96136]
- —
Updated dependencies [2b96136]
- —
Updated dependencies [2b96136]
- —@namzu/sdk@45.1.0
- —@namzu/browser@0.1.0
- —
Patch Changes
- —
2e2ea14: A skill's
allowed-toolsnow pre-approves its tools. It no longer restricts the tool set.The field comes from the Agent Skills format, and that format defines it this way: the listed tools skip the approval prompt for the rest of the turn that loaded the skill, and every other tool stays callable. Namzu read it the other way. After the
skilltool loaded a skill, the next batch was narrowed to the listed tools and the model was told to "restrict yourself to" them. A skill withallowed-tools: Read Greptherefore tookbashaway, and the model stopped doing the work.What breaks in
@namzu/sdk- —A loaded skill no longer narrows
ToolContext.allowedTools. If a host relied onallowed-toolsto confine the model, it should narrow the turn itself withQueryParams.allowedTools, a step'sallowedTools, ordenyrules. - —
ToolContext.adoptSkillScopeis deprecated. The kernel never supplies it, so a tool that calls it through?.now does nothing. It will be removed in the next major. UseToolContext.grantSkillTools. - —
createReviewHandler/createReviewPolicyinpromptandaccept-editsmodes now approve a batch without asking when every call it would ask about is covered by a skill loaded earlier in the turn. To keep asking about every call, passskillGrants: 'ignore'.planandstrictstill refuse such calls, and an operatordenyoraskrule, a destructive call, or a path outside the roots or the sandbox is never covered. Each approval made this way is written to the audit trail under the skill's name. - —The
bashtool runsbash -cinstead of/bin/sh -cwherever bash exists (the firstbashonPATH, then/bin/bash, then/usr/bin/bash), including for background jobs. On a host whose/bin/shisdashorbusybox sh(Debian, Ubuntu, Alpine), commands now run in bash; a command that relied ondashbehaviour, such asechoexpanding\n, behaves as bash does.BASH_ENV,ENV,SHELLOPTS,BASHOPTSandBASH_FUNC_*are removed from its environment. To keep/bin/sh, setNAMZU_BASH_SHELL=/bin/shin the environment of the process that runs the SDK. In a sandbox the tool now passes the guest/bin/sh -c '<launcher>' sh '<command>', which runs bash when the guest has it; a customSandbox.execorspawnDetachedthat inspected its arguments for['-c', command]sees the launcher instead. With no bash, the host still runs/bin/sh -c. - —A command line is read for the shell that runs it.
AuthorizationGate.evaluate,evaluateRuleandSkillGrantSet.coveringSkillcalled without a dialect read it for any POSIX shell (sh), so an allow rule orBash(<pattern>)entry no longer approves a line using a bash-only construct ($'…',&>,|&,<<<,[[, arrays, brace expansion,timeand others) unless the caller says the shell is bash. The kernel says so for thebashtool on a host that has bash; inside a sandbox it reads insh, because the guest may not have bash. A host calling the gate itself passescommandDialect: 'bash'in theToolCallContext(or{ commandDialect: 'bash' }as the last argument ofevaluateRuleandcoveringSkill) when the command will run in bash. Deny rules are unaffected: they still see every command. - —
parseAllowedToolssplits on whitespace as well as commas, and keepsTool(pattern)entries whole."read write edit"used to be one name and is now three. - —The skill manifest in the system prompt renders the field as
<pre_approved_tools>instead of<allowed_tools>. Theskilltool's notice lists what was pre-approved and what was ignored, and says every other tool remains available.
Added:
ToolDefinition.commandDialect(and thedefineTooloption),ToolCallContext.commandDialect,EvaluateRuleOptions, theShellDialecttype, andNAMZU_BASH_SHELL;ToolContext.grantSkillTools,ToolCallSummary.skillGrant,approve_tools.skillGranted,ReviewPolicyOptions.skillGrants,SkillGrantSet,compileSkillGrant,SKILL_TOOL_NAME_ALIASES,permissionPatternToRegExpSource, and aFrontmatterOptionsthird argument toparseFrontmatter(lists), which the skill loader uses so thatallowed-toolscan be a YAML list. Names are matched case-insensitively and through the format's aliases (Read→read,WebFetch→web_fetch).Bash(git status *)uses the CLI permission-table glob, applied to each command in the line.${CLAUDE_SKILL_DIR}and${NAMZU_SKILL_DIR}expand to the skill's directory. An unknown name is ignored and reported, and never widens the grant. A tool that is destructive for every input (the shippedwriteandrun_code) is ignored and reported too, because each of its calls is reviewed anyway.BashOutput,KillShell,TaskOutputandTaskStopmap tojob, andTaskCreate,TaskUpdateandTaskListtotask_*.ToolContext.grantSkillToolsreturns acommit(), and theskilltool records the grant only once it has delivered the skill's instructions. The grant also ends when the operator sends a message while the turn is still running (inboundMessagesor steering), through the newSkillGrantSet.clear(). ABash(<pattern>)entry never covers a line that redirects output into a file (git status > ~/.bashrc);/dev/nulland2>&1stay covered. A whole-toolBashentry grants the tool as it is, writes outside the working directory included, because bash has no path argument for the escalation check. An entry naming a tool thatallowedToolswithholds from the turn or step is ignored as not available, andSkillGrantToolResolvergains an optionalunavailablefield for that.@namzu/cli: a plugin skill'sallowed-toolspre-approves for the turn and no longer takes tools away.SKILL.mdfiles whoseallowed-toolsis a YAML list are now listed instead of refused. The[permissions]glob now comes from the SDK, and it matches the same commands as before.permissionChecksread a builtin tool's command line in the dialect that tool reports, as the runtime does. - —A loaded skill no longer narrows
- —
Updated dependencies [2e2ea14]
- —
Updated dependencies [2e2ea14]
- —@namzu/sdk@45.0.0
- —@namzu/computer-use@1.4.3
- —@namzu/anthropic@6.1.0
- —@namzu/ollama@2.2.4
- —@namzu/openai@4.1.0
- —@namzu/openrouter@3.0.1
- —
Major Changes
- —
eae8e31: Read-only agents start without asking in
promptmode. AnAgentcall that startsexplore, or an agent file withreadOnly: true, on the session's own provider and model no longer opens the "Start an agent" review inpromptoraccept-editsmode, andplanmode now lets it start instead of refusing it. Every call such a child makes is still reviewed as before, and its roster still has no tool that writes.Still asked about: a general-purpose agent, an agent file without
readOnly: true, a read-only agent givenprovider,effortor amodelother than the session's, an agent file that names another model, and any batch that includes one of those.strictstill refuses every launch no rule allows.What breaks: a
prompt-mode operator who relied on seeing and declining eachexplorelaunch is no longer asked, and aplan-mode turn can now start read-only agents.To keep the old behaviour — every launch asked about in
promptandaccept-edits, refused inplan— add anaskrule for the tool:{ "permissions": { "Agent": "ask" } }in
namzu.config.json(orpermissions: { Agent: ask }in~/.namzu/config.yaml). Anaskrule is an explicit review, which the read-only exemption never skips./permissions detailssays which rule is in force.
Minor Changes
- —
64489b3: Delegated work split into phases reads by phase, as the reference terminal's workflow view does. Nothing is removed and no key changes meaning; screens and transcripts look different.
- —The agent rail keeps a phased workflow together. Agents sharing a
workflowlabel stay on the rail as one piece for the parent turn: a finished phase is one line,✓ Phase 1 · 2/2 · 4.0s, and a live phase has its agents beneath it. Unlabelled agents are grouped by launch, as before. Under 24 rows a finished phase's line is left out. - —The rail's header counts the whole workflow:
● <workflow> · 1 running · 2/3 done · 6.5s · 18.0k tokens · ↓ / ctrl+t. Time and spend show from 96 columns. - —The agent cockpit opens on the phase still working, and on its first working agent, instead of on the first phase. Its header reads
2/3 agents done · 1 running · 7.7s · 18.0k tokenswhile work runs and3/3 agents · 9.5s · 27.0k tokens · doneafter, in place ofN active · N total. Phase rows add the phase's time; the agent pane is titled by its phase (Phase 2 · 1 agent) instead ofAgents · 1/2. - —The closing line adds the phase count when two or more were named, the tokens spent, and failures:
✻ Worked for 11s · 3 agents in 2 phases · 27.0k tokens.
- —The agent rail keeps a phased workflow together. Agents sharing a
- —
5fb75c2: Delegated work reads more like the reference terminal. Nothing is removed and no key changes meaning; transcripts and screenshots will look different.
- —Launch receipts. Each batch of agents one response launched now writes one
● Launched 2 agents · <workflow> / <phase> (ctrl+t to manage)row, with the agents named beneath it as a├/└tree. - —Completion rows read
✓ <name> · 1.7s · 9.0k tokensor✗ <name> · failed after 2.9s · <reason>instead of<name> · Completed · ctrl+t · agent details. A completed agent's final answer is attached collapsed; Ctrl+O opens it. The hint isctrl+o result · ctrl+t details. - —A closing line,
✻ Worked for 38s · 3 agents, ends a turn that launched agents. Other turns add nothing. - —The rail is a borderless tree under the footer: a
● <workflow> · N running · N queued · ↓ / ctrl+theader, one├/└branch per agent, and the running agent's activity on a⎿line beneath it on terminals at least 24 rows tall. Tool uses and spend now come before the model. It shows up to three agents on a 30-row terminal where it showed four, because each agent takes two rows there. - —The rail stays visible while an approval dialog is open, reduced to its header line. It used to disappear for as long as the dialog was up.
- —One waiting line. When the parent is only waiting on its agents, the
Waiting · <name>rows fold into✻ Waiting for N agents to finish. - —The
/effortpicker is a left-to-right slider on terminals at least 60 columns wide:default, the model's levels, then┆ orchestratein violet with<highest> + delegate by defaultunder it. ←/→ (and ↑/↓) move it, digits select, Enter applies, Esc goes back. Narrower terminals, and menus too long for one row, keep the vertical list. - —Orchestrate mode shows on the message box: the top border carries
orchestratein violet on the right, with a still colour gradient where colour is allowed; the footer'sorchestrateis violet. Below 40 columns the border stays plain.
- —Launch receipts. Each batch of agents one response launched now writes one
- —
4375e72: The terminal draws research turns the way the reference terminal does.
- —Web searches and fetches are one row naming the query or address,
✓ Web search("…")/✓ Web fetch(https://…), with a⎿line that readsSearching: …while the call runs and settles toFound 3 results in 4.1s,Did 1 search in 9.0sorReceived 7.5KB in 1.2s. Consecutive calls sit together without blank lines, and a long query is cut at the terminal's width with an ellipsis. A fetched page or result list stays behind Ctrl+O. They used to read✓ Web search · 9.0swith no query and a blank line between every row. - —Markdown tables are drawn as a box (
┌┬┐ ├┼┤ └┴┘) sized to the terminal, with long cells wrapped inside their column and**bold**,`code`and links drawn rather than shown as source. Where the columns cannot fit, the table becomesHeader: valuerecords separated by a─rule. A table streaming in no longer flashes raw| a | b |rows. Tables used to be a header, one rule and cells cut mid-word at 32 characters. - —The Working row counts the turn's output,
Working (46s · ↓ 1.1k tokens · esc to interrupt). - —A finished turn that took three seconds or more closes with
✻ Worked for 46s, as a turn that delegated work already did. - —Wrapped text in replies, your own messages and notices no longer starts a row with the space it wrapped at, so every row of a paragraph starts in the same column.
Nothing to change on your side.
- —Web searches and fetches are one row naming the query or address,
Patch Changes
- —
710e684: A wide row in the agent cockpit no longer runs one cell past its pane: the model or token count at the end of the row sat on the frame's padding and touched the border.
- —
4f14e2d: On a terminal under 64 columns the agent rail's header now counts agents done out of the total (
2/3 done), the figure its wide form and the agent cockpit's header give at the same moment. It used to print the agents still running over the total with no word after it (1/3), which read as one agent done. Nothing to change on your side. - —
45a79f3: Three places in the delegated-work screens named a key or text that did not work or was cut off:
- —An agent's
ctrl+o resultrow that had scrolled into history could not be opened. Now, once the live bodies are open, the next Ctrl+O opens the newest settled body in the output viewer, and ←/→ move to the others. With nothing settled, the second press still just folds the live bodies. - —While an approval dialog is open, the reduced agents rail no longer shows
↓ / ctrl+t. The dialog holds both keys, so neither reached the rail. - —On an 80-column terminal the effort slider's
<highest> + delegate by defaultsub-label moves left so it fits on screen instead of being cut. The spend warning wraps instead of being cut. The rows it uses stay reserved on the other stops, so the picker keeps the same height when the caret moves.
No configuration or API changes.
- —An agent's
- —
d9b1d30: An
Agentcall that names the session's ownmodel, with noprovideroreffort, now runs on the session's provider, as a call with nomodeldoes. It used to be looked up in the model catalogue, and when the session provider's listing failed or did not list that id, the child ran on any other connected provider that did — including a read-onlyexplorelaunch, which starts without the "Start an agent" review on the promise that it stays on the session's provider. To send a child to another provider, nameprovider(still reviewed).The agent cockpit's phase pane is titled
Phases. It used to readPhases · 1/2, the cursor position, above phase rows whose2/2means agents done, so a finished two-phase workflow read as one phase of two. Nothing to change on your side. - —
5bacecf: A transcript row written while the reply above it is still streaming no longer reaches scrollback before that reply. It used to settle first on a short terminal; when the reply then finished it was placed below rows already printed, so one row was printed twice and the reply's sentence was never printed. Nothing to do on upgrade.
- —
f934968: A transcript row keeps its column and its wrap when it settles into scrollback. Settled rows used to lose the one column of padding live rows have, so a finished screen mixed rows starting at column 0 and column 1, and a long settled row wrapped two columns wider than it had while live. The brand header, printed the same way, moves one column right with them. Nothing to do on upgrade.
- —
0a39453: A reply that is still streaming is drawn where it stands in the conversation. It used to be drawn below every finished row, so a row written while it streamed, such as the launch receipt of an agent running in the foreground, appeared above the text that came before it and the two swapped when the turn ended. Rows no longer move after they are drawn. Nothing to do on upgrade.
- —
a31aeb7: A reply taller than the terminal keeps its last line once it finishes. The redrawn part of the screen is now held one row shorter than the terminal, so the renderer no longer clears the screen and replays the session on every frame of a long reply, and no longer erases the reply's final line when the turn settles. While such a reply streams, its newest rows stay on screen; the whole reply reaches scrollback when it ends.
A list item or blockquote containing a pipe directly under a markdown table is drawn as a list item or quote again, not as an extra table row.
- —
e3c9228: Typing that reaches the composer in one long read (keystrokes queued behind a busy screen, with no bracketed-paste markers) is now inserted as typed text. It used to become a
Pasted textchip once it passed 80 characters, and the chip was joined back to what had been typed before it with a blank line — so the message the model received could have a word split in two ("subag" / "ents"). A bracketed paste over 80 characters, and any unbracketed chunk containing a newline, is still held as a chip. Nothing to do on upgrade. - —
Updated dependencies [4375e72]
- —
Updated dependencies [1b65e2e]
- —@namzu/sdk@44.3.0
- —@namzu/openai@4.1.0
- —@namzu/anthropic@6.1.0
- —@namzu/google@1.1.0
- —
Patch Changes
- —
9bd0c99: Plan mode now holds inside delegated work.
@namzu/sdk: new optionalBaseAgentConfig.reviewAllowedCallsandAgentTaskContext.reviewAllowedCalls.AgentManagerstamps the spawning context's function onto every child (after theconfigBuilderruns) and onto the child's spawn context, so grandchildren inherit it too;SupervisorAgenthands its own to its workers, andReactiveAgentandSupervisorAgentpass it toquery(). Before, only the turn a host calledquery()with sawQueryParams.reviewAllowedCalls: inside a child, a batch a rule allowed, or one an approval earlier in the child's turn covered, ran without reaching the borrowed review handler. Nothing changes for a host that never sets the field. A child config that sets its own value keeps it, but it is OR-ed with the inherited one: a child can ask for more review and can no longer answerfalseover a parent that answerstrue. A host that builds its ownAgentTaskContextfor aTaskSchedulershould set the field from the function it passes its ownquery().@namzu/cli: plan mode entered with Shift+Tab while a sub-agent runs now refuses that sub-agent's next change even when apermissionsrule allows it; before, the child ran it./resumeon a parked turn now continues it under the permission mode the operator is in, read at each decision like a new turn's, for the turn and its sub-agents; before, the interactive terminal resumed it underautowhatever the mode, so/resumein plan mode let its changes through and a Shift+Tab during it did nothing. With nobody asked on a resumed turn,promptandaccept-editsstill approve what the rules leave to review;planandstrictnow refuse.namzu execresumes under the mode it was started with, as before, and in plan mode that now also covers changes a rule allows. - —
Updated dependencies [9bd0c99]
- —@namzu/sdk@44.2.0
- —
Minor Changes
- —fad53e3: Shift+Tab now changes the permission mode while a turn runs, and its only reply is the footer. It used to be refused mid-turn with "Permissions were not changed. Finish or stop the current work first." (once per press), and every accepted press appended "Permissions: <mode> for this session. …" to the transcript. Now the running turn's next approval decision, the delegated turns that borrow its review, and every later turn are decided under the new mode; an approval dialog already on screen keeps the mode it was asked under, entering plan mode refuses the next call that would change something, and leaving it approves nothing already refused. Each change is recorded as
approval_policy_changedin the session log before it takes effect, and the model is told once./permissionsstill answers in the transcript. The footer's hold mark is‖instead of⏸, an emoji code point that Windows Terminal draws as a blue two-cell tile; the same mark replaces it on the interrupted-turn notice and the paused-queue line. - —f2ec6e7: The model's plan is drawn once, as a checklist in the transcript. Consecutive task calls fold into one block with a header in words (
Added 2 tasks,Started · <subject>,Completed · <subject>,Tasks · 1/2 done) and the checklist as it stood afterwards;/tasksdraws the same checklist. No task id, owner, JSON argument or model receipt (Task created: <uuid> — "…" [owner: namzu],1 tasks: 0 completed, …) is shown any more. The marks are one-cell text characters with exactly one space before the subject —□pending,■in progress (bold),✓completed (dimmed, struck through),✗failed — instead of☐/☑/☒/◐, which emoji-capable fonts drew as two-cell colour pictures. The eight-row task list above the composer is gone; a single row naming the current step appears there only while the checklist is out of view. A system notice identical to the row directly before it is no longer printed a second time.
Patch Changes
- —
51af1b3: Plan mode now refuses a change that a permission rule allows. Before, a batch whose every call a rule allowed (for example
permissions: { bash: 'allow' }) ran without reaching the review handler, so a turn in plan mode, whether it started there or the operator entered it with Shift+Tab mid-turn, still rantouchor any other allowed command, while the model had been told that every change is refused. The CLI now asks the kernel to send such batches to review while it is in plan mode; nothing changes in any other mode.For SDK hosts:
QueryParams.reviewAllowedCalls?: () => booleanis new and optional. Read once per batch;truesends a batch a rule allows, or a grant from earlier in the turn covers, toresumeHandlerinstead of running it, each call carrying the gate's decision inauthorization. A rule'sdenystill refuses. Absent, behaviour is unchanged. - —
406e7a2: A task removed with
task_updatestatusdeletedis now distinguishable on the stream: itstask_updatedevent carriesdeleted: true(the SSEtask.updatedevent too), with the subject and status the task had when it went. Before, a removal arrived as an update that changed nothing, so the interactive terminal kept drawing the removed task as an open step in the checklist. It now drops the task and writesRemoved task · <subject>. The field is optional and absent on every other update; a consumer that ignores it sees the same events as before. - —
Updated dependencies [51af1b3]
- —
Updated dependencies [406e7a2]
- —
Updated dependencies [0d23de8]
- —@namzu/sdk@44.1.0
- —fad53e3: Shift+Tab now changes the permission mode while a turn runs, and its only reply is the footer. It used to be refused mid-turn with "Permissions were not changed. Finish or stop the current work first." (once per press), and every accepted press appended "Permissions: <mode> for this session. …" to the transcript. Now the running turn's next approval decision, the delegated turns that borrow its review, and every later turn are decided under the new mode; an approval dialog already on screen keeps the mode it was asked under, entering plan mode refuses the next call that would change something, and leaving it approves nothing already refused. Each change is recorded as
Major Changes
- —
a75f5c2:
namzu runandnamzu run-streamare removed. Usenamzu exec.Before After namzu run "<prompt>"namzu exec "<prompt>"(ornamzu e "<prompt>")namzu run-stream "<prompt>"namzu exec --json "<prompt>"exectakes every option the two old commands took, with the same meanings:--cwd,--provider,--model,--effort,--skills,--continue/-c,--resume,--session,--gate,--gate-retries,--max-iterations,--token-budget,--wait-for-provider,--permission-mode,--trust,--yoloand--. The default mode prints the reply exactly asrundid and keeps its exit codes (0, 1, 2, 64, 75, 77, and death by SIGTERM/SIGHUP/SIGINT).--jsonwrites the same NDJSON events, with the same kinds and fields, and the same exit codes (0, 1, 75, 77) thatrun-streamdid. Typingrunorrun-streamnow fails with commander'sunknown commanderror, exit 64, and a line naming the replacement. Update scripts, CI jobs and host UIs that spawn either command.New:
--output-schema <file>onexec. It binds the final answer to a JSON Schema through the provider's native structured output, in either mode. Before,--output-schemawas accepted only before the command, for the TUI, and refused everywhere else; given beforeexecit is still refused, now with a message that says to pass it afterexec. A schema file that cannot be read or represented exits 64 (in--jsonmode: anerrorevent and exit 0).Also changed:
- —
namzu exec --session <id>without--jsonis refused with exit 64.runaccepted--sessionand ignored it, answering against no history; the default mode resumes with--continueor--resume <id>. - —
namzu residentrefuses--jsonand--output-schema, which it would otherwise have accepted and ignored. - —The
--log-formathelp and the TUI's--output-schemahelp nameexec.
- —
Minor Changes
- —
ab9108b: Refresh the Zen and Zen Go model catalogue in the background on every launch. The interactive TUI,
resume,run,run-stream,acp,drain,resident runand the background resident runner start one refresh that never delays startup, gives up after 30 seconds and is cancelled when the command ends. When it lands, the model picker and routing use it immediately, including for providers already built, and it is saved as a last-good copy atcli/zen-catalogue.jsonunderNAMZU_HOME. When it fails, the session keeps the last-good copy, or otherwise the bundled catalogue, and logs one warning. A damaged copy on disk is ignored rather than read in part. A model the service serves with no known wire format is not offered in the picker, since the CLI has no setting that names a protocol for it.This is on by default, and each of those launches now reads OpenCode's documentation pages, models.dev and the two Zen
/modelsendpoints over the network. To keep the previous behaviour (bundled catalogue only, no network read), setmodelCatalogueRefresh: falsein~/.namzu/config.yaml, orNAMZU_MODEL_CATALOGUE_REFRESH=0.
Patch Changes
- —128913f: The TUI says "turn" where it still said "run" for the unit of work that no
longer exists:
/cost,/statusand the status panel label spend as "current or latest turn", a message held after a paused turn says so, the/confignotice names turn limits, and the headless trust refusal says a headless turn approves tools without asking, andnamzu doctor's remediation for an unusable fallback says turns will still start. Wording only; nothing a script parses changed. - —Updated dependencies [ab9108b]
- —@namzu/zen@2.5.0
- —
Major Changes
- —
518b0d3: Every turn the CLI starts keeps its newest 10 checkpoints instead of all of them. That covers interactive turns,
namzu run, resumed and drained turns, and delegated child sessions. A turn takes a checkpoint every iteration plus one per tool review, and nothing set a limit, so a long session kept every one. On one machine that came to 19,014 checkpoint files and 6.33 GB.What changes for you. Only the newest 10 checkpoint documents of each turn stay in
<session-id>/checkpoints/, andnamzu drainreports at most that many per turn. A checkpoint whose decision is still outstanding is never pruned. Resuming is unaffected, because every resume reads the checkpoint it was handed or the newest one. If you inspect intermediate checkpoints by hand, copy them out while the turn is still going. - —
b064cea: The OS sandbox is now off by default. Shell commands and file tools run on the host under the permission system, the way other coding agents run them: each shell command goes through your permission rules and mode, and a file tool's path outside the working directory and the added directories is shown to you as an approval request (
Outside the working directory: <path>) instead of being refused. It is asked every time —auto,--yolo,plan(for a read) and an earlier "allow all tools for this session" included — whilestrictand adenyrule still refuse it. A headless turn refuses it: pass the directory with--add-dir(oradditionalDirectories) when an unattended run needs it. Each answer is an audit record.To keep the previous behaviour (every command confined), write
sandbox.enabled: trueinnamzu.config.jsonor~/.namzu/config.yaml. The sandbox is also on, without that line, whensandbox.requireIsolationnames a control orsandbox.workspaceisephemeral.sandbox.enabled: falsekeeps working as before.With the sandbox on, a
bashcall may setdangerously_disable_sandbox: trueto run one command on the host. It is asked about every time,autoand--yoloincluded (planandstrictrefuse it); it is refused when nobody can be asked, unlesssandbox.allowUnattendedEscape: true;sandbox.allowEscape: falserefuses it always. Each approval or refusal is an audit record./add-dirof a directory outside the working directory now asks before adding it, deciding after links are followed (a link inside that points outside is asked about under the path it leads to, and that canonical path is what is added). A directory inside the working directory is no longer added: the tools already reach it. The system prompt states where tools run and how to reach past that, and on WSL where the Windows drives are and how to start a Windows program. - —
7cac6dc:
#noteand/memory addnow save a typed memory file instead of appending a bullet to<project>/.namzu/MEMORY.md.What changes for you:
- —Where a note goes.
#note <text>and/memory add <text>write<name>.md(typeproject;/memory add --type user|feedback|project|reference <text>picks another) into the project's stored memory —<NAMZU_HOME>/projects/<slug>/memory/by default — the same files the model'ssave_memory,search_memoryandread_memoryuse. The terminal names the file./memory --user add <text>is unchanged and still appends to~/.namzu/MEMORY.md. To keep writing a note into the curated project file, edit<project>/.namzu/MEMORY.mddirectly; it is still read into every turn. - —Old notes are copied only when you ask, and your file is never changed. The project's curated
MEMORY.mdis not rewritten, at launch or ever: nothing can tell a bullet#noteappended from one you wrote. When it holds top-level bullets, the launch says how many, once per curated file, and/memory import-notescopies every top-level bullet (its first line) into a typedprojectmemory, skipping any already stored — by an earlier import, a#notewith the same text, or a copy you archived — so running it twice creates no duplicates. The bullets stay curated text in every turn until you delete them from the file yourself. - —A one-time move of the JSON store on first launch. An earlier JSON memory store (
index.json+content/) in the same directory is imported with its ids and renamed*.migrated; the launch says so. A record too large for a memory file (over 256 KiB) is not imported and is named, with the retired file that still holds it. - —The prompt. Every turn carries a
## Stored memories (index)section — one line per memory you or the model saved, at most 200, yourfeedbackandusermemories first. What the session memory promoter (orcompaction.consolidate) writes after a turn is searchable but not listed, so a turn's record does not change the next turn's system prompt — and the curated sections are renamed## Curated memory (all projects)and## Curated memory (this project)(they were## Durable memoryand## Project memory). Anything that matched those headings in a captured prompt must match the new ones. - —
/memory showlists stored memories' index lines before the curated files, and what turns recorded on their own in a separateRecorded by turns (N)section, so those records are visible even though the prompt's index leaves them out. - —Note names are short. A note's file is named after its first words, at most 32 characters, so its index line keeps room for the note itself.
Nothing in the CLI's library exports changed.
- —Where a note goes.
- —
3e7a97b: The CLI moves to the SDK's session → turn → message model and to one state layout under
NAMZU_HOME(default~/.namzu). Conversations, checkpoints, memory and resident state written by 26.x are not read by this version.Before you upgrade.
- —Run
namzu drainon 26.x until it reports nothing parked. A turn still parked on a decision or a provider wait when you upgrade cannot be resumed. - —Export any conversation you want to keep with
namzu history --session <id> > conversation.jsonon 26.x (with no--session, the latest one in the folder); it prints the messages as JSON. There is no migration: nothing is imported, so nothing is lost silently or brought back half-read.
What changes for you
- —Where state lives. Each working directory gets
~/.namzu/projects/<slug>/, where the slug is the directory's canonical path with every character outside[A-Za-z0-9]replaced by-. A session is<session-id>.jsonlthere, with its child sessions, checkpoints, tasks, feedback, goals, tool-result spills and file-history snapshots under<session-id>/. Memory moves toprojects/<slug>/memory/, resident state toprojects/<slug>/residents/, and git worktrees toprojects/<slug>/worktrees/.~/.namzu/index.sqliteis an index rebuilt from the logs whenever it is missing or out of date; deleting it loses nothing. The CLI still never writes into the working directory;.namzu/there is read only, for the agents, skills, commands, plugins andMEMORY.mdyou put in it. - —Old state is left alone and reported.
namzu statenow prints reportversion: 2with alegacycategory: the old top-levelstate/,sessions/,titles.json,desktop-sessions.json,delegation-history/,checkpoints/,tenants/,goals/,feedback/,learning/,residents/,worktrees/andmemory/, and everyprojects/<uuid>/directory. It lists their paths and sizes and never opens, moves or deletes them; remove them yourself once you have exported what you need. A newprojects/<slug>/is never reported as legacy. - —One turn at a time per session. A session has at most one active turn.
namzu runandnamzu run-streamagainst a session whose turn is still running, paused or interrupted now exit 75 (EX_TEMPFAIL) and name that turn: on stderr forrun, as an NDJSON{"kind":"error","code":"turn_in_progress",…}event forrun-stream. 75 keeps its existing meaning for a provider pause too; a wrapper that already retries later on 75 needs no change. In the TUI the refusal offers/resumeor the new/abandon, which closes the paused turn so the next prompt can start. - —A rate limit on the first request pauses the turn. A provider rate
limit or outage on a turn's first request used to fail it (
namzu runexit 1) with nothing to continue; it now pauses it at a checkpoint like the same fault later in the turn:namzu runexits 75 naming the checkpoint,--wait-for-providerwaits and resumes it, and/resumeornamzu draincontinues it. - —
/agents runsis now/agents batches, with no alias./agents runsprints the unknown-subcommand usage. - —NDJSON gains ids.
run-stream'sdoneandusageevents carrysessionIdandturnId, and apausedevent names itsturnIdinstead of a run id. - —Hooks. The events
run_start,run_endandrun_interruptareturn_start,turn_endandturn_interrupt; a config that names an old event is refused at load with a message naming the new one. A hook's stdin carriesturn_id(notrun_id) and, onsubagent_stop,parent_session_idandparent_turn_id(notparent_run_id); its environment carriesNAMZU_TURN_ID(notNAMZU_RUN_ID).session_idandNAMZU_SESSION_IDare always set.session_startandsession_endhooks receive no turn id: the CLI no longer invents one for them. - —
namzu drainkeeps its flag names and exit codes (0, 1, 64, 77), but--storemeans something else: it was theruns/directory a checkpoint store wrote to, and it is now the namzu home (NAMZU_HOME,~/.namzuby default) whoseprojects/hold the session logs. A--storewithout aprojects/directory is refused with 64, so a wrapper that still passes its oldruns/path must pass the home instead. A scope the store does not hold (an unknown session, one under another project or tenant, a child session) is also 64; state that cannot be read is 1. It finds parked turns through the session index, takes each session's lease, and continues the same turn from its checkpoint; a turn whose session lease another worker holds is skipped and reported. - —Delegation history is not carried over. The history block and
/agentsread finished child sessions from their logs; children recorded by 26.x do not appear. - —Checkpoints. A session keeps each turn's newest 10 checkpoints under
<session-id>/checkpoints/; one an open decision references is never pruned./restoresnapshots are under<session-id>/file-history/. - —Crash dumps are gone. The session log and per-iteration checkpoints hold
everything an interrupted turn needs, so no
emergency/dumps are written. An interactive session that was interrupted closes that turn as interrupted when you send the next prompt. - —A stopped process gives its conversation back. On SIGTERM, SIGHUP (a
closed terminal) or SIGINT, the TUI,
namzu runandnamzu run-streamrelease the conversation's writer lease first, then stop the turn, close the session (tool servers, background jobs, thesession_endhook) and give the terminal back, and then die of the signal they were sent: a wrapper sees 143, 129 or 130 as before. The turn is left interrupted, so/abandon,/resume,namzu drainor the TUI's next prompt take it at once; before, they were refused as "leased by a live writer" for up to five minutes. A second signal exits immediately.run-streamwrites{"kind":"error","code":"terminated",…}and a finaldonebefore it exits, andrunnames the session on stderr. SIGKILL still leaves the lease to expire. The message follows where the signal found the turn: a turn that had already ended (the session still closing) is reported as recorded, not interrupted, andrun-stream's onedoneis then the turn's own; arun --wait-for-providerstopped during its wait says the turn is paused at its checkpoint, and does not resume it.
- —Run
Patch Changes
- —
96ac3ec: A session never writes generated state into its working directory, and a session started in the home directory no longer reads the same memory file twice.
Every entry point —
namzu,namzu run,namzu run-stream,namzu drainand resident runs — keeps its sessions, memory and task state under the application home (NAMZU_HOME, else~/.namzu), in the working directory'sprojects/<slug>/, and files them under that directory's one Project.<cwd>/.namzuis only read, for the agents, skills, commands, plugins andMEMORY.mdyou keep there.Started in
$HOMEwith noNAMZU_HOME, the project's.namzu/MEMORY.mdand the user's~/.namzu/MEMORY.mdare one file. It was injected into every prompt twice, under both headings. It is now read once, as the user memory. - —
3641102: The turn-start repository snapshot (
git statusand recent commits, sent on a send's first request) now reaches the model as request-only context after the conversation, not as a system message. On Anthropic, each new send used to change the system prompt and re-read the whole conversation uncached. Now the cached conversation is kept and only the snapshot's own tokens are new.What the model reads changes in two ways: the snapshot arrives in a user-role message rather than a system message, and it follows the line
Current step context (runtime-generated; not a new user request):, which every request-only context message opens with. The snapshot text itself is unchanged. - —
Updated dependencies [8805360]
- —
Updated dependencies [a729b17]
- —
Updated dependencies [9355755]
- —
Updated dependencies [755a81a]
- —
Updated dependencies [cb1f00c]
- —
Updated dependencies [3641102]
- —
Updated dependencies [933ba6d]
- —
Updated dependencies [3e7a97b]
- —
Updated dependencies [a84dc1c]
- —
Updated dependencies [b064cea]
- —
Updated dependencies [9238347]
- —
Updated dependencies [a14b013]
- —
Updated dependencies [3641102]
- —
Updated dependencies [3641102]
- —
Updated dependencies [3e43fc2]
- —@namzu/sdk@44.0.0
- —@namzu/anthropic@6.0.1
- —@namzu/openrouter@3.0.1
- —@namzu/zen@2.4.0
- —@namzu/computer-use@1.4.3
- —@namzu/ollama@2.2.4
- —@namzu/openai@4.0.0
- —
deepseek
1 release
Patch Changes
- —567ada8: Updated package examples and evaluation fixtures for the SDK's
ToolRegistryremoval. Browser, computer-use and LSP documentation now show toolsets; the evals usetoolsetswhen they call the kernel. Provider changes are test fixture migrations only, with no provider runtime API change. Consumers that pass tools to the SDK should usetoolsets: readonly Toolset[]and wrap their definitions withtoolset(source, definitions).
- —567ada8: Updated package examples and evaluation fixtures for the SDK's
evals
2 releases
Patch Changes
- —567ada8: Updated package examples and evaluation fixtures for the SDK's
ToolRegistryremoval. Browser, computer-use and LSP documentation now show toolsets; the evals usetoolsetswhen they call the kernel. Provider changes are test fixture migrations only, with no provider runtime API change. Consumers that pass tools to the SDK should usetoolsets: readonly Toolset[]and wrap their definitions withtoolset(source, definitions).
- —567ada8: Updated package examples and evaluation fixtures for the SDK's
Major Changes
- —
3e7a97b: Requires
@namzu/sdk >=44.0.0(was>=5.0.0): the suites build their cases on the SDK's turn API (evalTurnFromTurn), which does not exist before 44. An earlier@namzu/evalsbreaks against SDK 44, and this one does not load against an older SDK. No case, score or report shape changed.What to do: upgrade
@namzu/sdk,@namzu/cliand@namzu/evalstogether.
Patch Changes
- —018187a: The kernel suites remove each case's scratch directory when the case ends and keep the session state inside it. Every case used to leave a directory with a full state tree in the system temporary directory.
- —
files
1 release
Patch Changes
- —
2d4ff9a: Shipped text no longer names one particular application built on namzu. Nothing to do to upgrade: no type, default or behaviour changes.
- —
@namzu/sandbox: when a self-hosted Firecracker orchestrator returns a network-mode (mtls) agent handle and the backend was given no client certificate, the error now tells you to passmtls: { ca, cert, key }in the backend config. It used to name environment variables that only one host defines, which no other installer has. The message still starts withfirecracker: orchestrator returned an mtls agent handle but no client cert material was injected, so code matching on that prefix keeps working. - —Doc comments in the published
.d.tsfiles and sources (ContainerBackendConfig.labels, the ACI and Azure Blob name options, the sandbox mount-source types) say "the host" or "the consumer", and label examples use the placeholderacme.namespace. Themicrovmtier's (MicroVMBackendConfig,AgentSnapshotRef,OrchestratorNetworkPolicy) describe the orchestrator as a self-hosted one the host runs, not as namzu's own: namzu ships only the client. - —Earlier entries in the
@namzu/sandbox,@namzu/sdkand@namzu/anthropicCHANGELOGs are reworded the same way; the@namzu/filesCHANGELOG named no one and is unchanged. Versions already on npm keep their old text.
- —
- —
3 releases
Patch Changes
- —023224a: A failed OpenAI or Google request now reaches the host with the provider's own type, code, parameter and request ID, as an Anthropic failure already did, so Details read "Status: 403 · Provider said: “…” · Type: invalidrequest_error · Code: model_not_found" with "Request ID: …" on its own line. The error's
detailfor a vendor SDK error whose message is only a sentence is now a JSON body of those fields ({"error":{"message","type","code","param"},"request_id"}) instead of the bare sentence; readproviderCodeor theDetailsof a host rather than matching the sentence text. The Google driver now reads its refusal body and request-ID header. The vendor project line in Details reads "Project: proj…".
- —023224a: A failed OpenAI or Google request now reaches the host with the provider's own type, code, parameter and request ID, as an Anthropic failure already did, so Details read "Status: 403 · Provider said: “…” · Type: invalidrequest_error · Code: model_not_found" with "Request ID: …" on its own line. The error's
Patch Changes
- —
49491b9: The drivers now report more accurately how a response ended. The runtime uses this to tell a tool call the output limit cut off from one the model wrote badly.
- —
@namzu/anthropic: a tool call whose JSON does not parse no longer fails the whole stream with "the provider stream returned malformed data". The block close and the finish reason now reach the runtime.model_context_window_exceededis reported aslength, andrefusalascontent_filter. Both used to read as a normalstop. - —
@namzu/bedrock:model_context_window_exceededis reported aslength, andguardrail_intervenedascontent_filter. A tool call opens with the id the driver keeps, so its arguments never arrive before an id. - —
@namzu/http: the OpenAI dialect mapsfinish_reasoninstead of passing the server's string through.function_callbecomestool_calls;max_tokensandmax_output_tokensbecomelength;model_length,context_length,context_length_exceededandmodel_context_window_exceededbecomelengthwithfinishDetail: 'context_window';eos,eos_token,end_turnandstop_sequencebecomestop.errorfails the stream with aProviderRequestError(kind: 'server'), as OpenRouter's does. A value it does not know reports no finish reason at all, which the runtime reads as a response that did not say how it ended, rather than as a normal finish. The Anthropic dialect gets the Anthropic mapping above and opens a tool call with the id its arguments carry. - —
@namzu/openrouter:finish_reasonis mapped the same way.error(the upstream model failed mid-generation) now fails the stream with aProviderRequestError(kind: 'server'). - —
@namzu/deepseek:insufficient_system_resourcenow fails the stream with aProviderRequestError(kind: 'server'). It used to read as a finished answer. - —
@namzu/openai: Codex'sresponse.incompleteis reported aslength, or ascontent_filterwhen that is the stated reason, with its usage. The stream used to end with no finish reason, so auto-continuation never ran. - —
@namzu/anthropic,@namzu/bedrockand@namzu/http's Anthropic dialect: amodel_context_window_exceededstop also carriesfinishDetail: 'context_window', so the runtime does not ask the model to continue a reply that filled the whole context window. - —
@namzu/anthropic,@namzu/googleand@namzu/openai(Codex): the list of sources a driver appends after a hosted search now carriescontentOrigin: 'driver'on its stream chunk. The text is unchanged. Without the mark, a tool call the output limit cut off before that list would be reported as malformed.
If you branch on
finishReason, expectlengthorcontent_filterwhere you sawstopfor these cases. - —
- —
Minor Changes
- —4375e72: A provider-hosted web search now says what it searched for.
StreamChunk.delta.hostedTool, and so thehosted_toolsession event and thehosted.toolSSE event, may carryquery(the search query),url(a page the provider opened instead of running a query) andresults(how many sources it reported). Codex fills them from the search call's action, Anthropic from the search block's streamed input and its result list, Google from the grounding metadata. Each field is optional and absent when the provider does not say; nothing that already readsid,nameandstatuschanges. A custom driver can start emitting them whenever it knows them.
- —4375e72: A provider-hosted web search now says what it searched for.
live
2 releases
Major Changes
- —567ada8:
NamzuQueryConfignow passestoolsets: readonly Toolset[]to the SDK instead oftools: ToolRegistry. If you construct aNamzuModel, replace itstoolsfield withtoolsetsand wrap tool definitions withtoolset(source, definitions). This release requires@namzu/sdk >=48.0.0; use@namzu/live2.x with SDK 44–47.
- —567ada8:
Major Changes
- —
3e7a97b: Requires
@namzu/sdk >=44.0.0(was>=34.0.0). The SDK's run became a turn inside a session, and the ids@namzu/livereports from the model changed with it. Earlier@namzu/liveversions break against SDK 44.- —
LiveModelEvent: theusage,completedandcancelledevents carrysessionIdandturnId(the model's own session and turn — forNamzuModel, the SDK session and turn the query ran as) instead ofrunId. - —
LiveSessionEvent: theusageandturn_completedevents carrymodelSessionIdandmodelTurnIdinstead ofrunId. TheirturnIdis still the live session's own turn. - —
LiveTurnResult.runIdis replaced bymodelSessionIdandmodelTurnId. - —
LiveErrorCodeloses'run_not_speakable'and gains'turn_not_speakable': the errorNamzuModelraises when the SDK turn did not complete with a stop reason it can speak. A caller that switches onerr.codemust match the new value.
NamzuModelmaps the SDK turn'sresult,status,tokenUsageandstopReasonexactly as before. AcreateQueryParamscallback passesturnConfiginstead ofrunConfig, and anInMemorySessionLogassessionLogwhere it used to pass anInMemoryRunStoreasrunStore.What to do: rename
runIdreads tomodelTurnId(andmodelSessionIdwhere you need the session), match'turn_not_speakable'where you matched'run_not_speakable', and update the query params your callback builds. - —
- —
lsp
1 release
Patch Changes
- —567ada8: Updated package examples and evaluation fixtures for the SDK's
ToolRegistryremoval. Browser, computer-use and LSP documentation now show toolsets; the evals usetoolsetswhen they call the kernel. Provider changes are test fixture migrations only, with no provider runtime API change. Consumers that pass tools to the SDK should usetoolsets: readonly Toolset[]and wrap their definitions withtoolset(source, definitions).
- —567ada8: Updated package examples and evaluation fixtures for the SDK's
namzu
1 release
- desktop-latestpre-release
Namzu Desktop 0.2.1 — preview for Windows
The desktop app for working with AI agents: projects, conversations, Pals with their own computer, and the Namzu terminal agent bundled in.
If you already have Namzu Desktop installed, it updates itself to this version — no need to download again.
Download (new install):
Namzu-Setup-0.2.1.exebelow (about 506 MB). Windows 10 or 11, 64-bit Intel or AMD.SHA-256:
fbdb7b790ffedccee2c370c3cd030670989ef5e5181e9d5c266c8e8eb0e0aa31To check it in PowerShell:Get-FileHash .\Namzu-Setup-0.2.1.exeWhat's new since 0.2.0
- —A Pal now answers on the model you chose for it: the conversation follows the Pal's current model instead of the one it opened with, and the first message no longer pins the model shown.
- —The left icon rail shows Spaces, Scheduled and Plugins as in progress — they do not open yet and return when that work lands.
- —The menu bar reads like an app, not a browser: the View menu drops Reload, Force Reload and the developer tools, keeping zoom and full screen.
- —The bundled terminal agent is now 36.1.1.
- —A reliability fix for free-model access.
This preview is not code-signed
Windows warns you before it runs. That is expected for this preview.
- —"Windows protected your PC": choose More info, then Run anyway.
- —"Open File – Security Warning" with "Unknown Publisher": choose Run.
Installing
- —Installs for your user only. No administrator rights are needed. Adds Desktop and Start menu shortcuts.
- —Takes a minute or two; the app opens by itself when it finishes.
- —To start chatting, connect a model provider: paste an API key, or sign in with Claude Code or Codex and Namzu finds that sign-in.
Pal computers
A Pal can have its own Linux computer, which runs on Windows Subsystem for Linux (WSL). Chatting with a Pal works without a computer.
Updates
Namzu checks for new versions and installs them for you. You can change this in Settings ▸ Updates.
Uninstalling
Use Settings ▸ Apps ▸ Namzu. This removes the app and its shortcuts, and keeps your data:
%APPDATA%\Namzu(app settings) and.namzuin your user folder (conversations, Pals, projects).Checked before release
These exact bytes were downloaded from this page and their SHA-512 matches the update feed.
sandbox
9 releases
Major Changes
- —
c82b951: Change the local Pal provider's default foreground ownership from strict command lifetime to computer lifetime. Rebuild the shipped local-computer image from this release to use the new default. To retain the previous behavior and continue using an existing strict image, set
normalExitPolicy: 'strict'when callingcreateLocalVirtualComputerProvider. A successful foreground launcher can leave Blender or another application open without retiring the entire computer; the application remains owned by that guest allocation until the computer stops. An authenticated execution-reservation acknowledgment prevents an upgraded host from silently admitting this policy against an older image.Generic workers and registered background jobs retain strict process ownership. Failed launchers and timed-out or cancelled commands cannot transfer live descendants; if termination cannot be confirmed, the worker retires the allocation. A completed launcher does not claim its surviving application has terminated.
CLI Pal computers use the same new default. Set
NAMZU_PAL_COMPUTER_NORMAL_EXIT_POLICY=strictbefore starting the CLI or desktop host to retain their previous command lifetime and use an older strict image. - —
7f5ac50: Update the bundled local Pal computer's normal Blender launcher from the distribution Blender 3.4 to checksum-pinned Blender 5.2.2 LTS and its Godot launcher from Godot 3.2 to checksum-pinned Godot 4.7.2. Godot 3 projects must be converted and tested before opening with the new default. Preserve the original project and use
/usr/bin/godot3for explicit legacy work;/usr/bin/blenderalso remains available. A custom older image retains its own application versions.Normal Godot launches use the guest's OpenGL compatibility renderer. Blender MCP startup addon update checks and telemetry are disabled for guest launches. These settings do not install a server or addon automatically. Rebuild the local image explicitly, then stop and reopen an idle Pal computer to select it; rebuilding a tag does not replace a running allocation or change saved guest files.
Minor Changes
- —
7f5ac50: Add optional
MCPClientConfig.transportFactoryfor caller-owned transports, preserving built-in transport configurations and the existing MCP protocol lifecycle without launching a host subprocess as fallback.Add optional
Sandbox.openStdiowith allocation-owned interactive guest pipes, byte streaming, confirmed process-group shutdown and explicit per-request device operation barriers. Idle services permit operator takeover; an unknown issued application operation permanently fences the allocation until computer stop and cannot be cleared by a late response or closing an external application's MCP server. The local Pal worker must be rebuilt alongside the runtime to advertise interactive support. Older workers refuse this capability before spawning; ordinary foreground, background, file and desktop APIs remain available.Keep quiet interactive services alive with bounded worker heartbeat frames, discarded by the matching runtime before application output and without changing request ownership or uncertain-effect barriers. Rebuild the worker and load the matching runtime together for the interactive protocol.
- —
0f5aa96: Ready-computer Pal prompts now default to general application work guidance: understand references and acceptance criteria, make reversible changes, observe and correct the actual result, and validate saved files and requested exports before delivery. SDK hosts that need the previous prompt behavior can pass
workGuidance: 'basic'tobuildPalSystemPrompt. This guidance shapes model behavior; it does not certify semantic correctness.The SDK
readtool now refuses real PNG, JPEG and WebP bytes as text. Mount the optionalview_image/createViewImageToolfor saved-image inspection, or use a format-aware binary reader when pixels are not the desired result.getBuiltinTools()retains its previous tool roster. Saved artifact images provide no GUI screenshot authority.The CLI and desktop Pal runtime now explicitly mount
view_imageandimport_reference_images. Image import is a reviewed file-write operation using only the current operator input and admitted guest. Original image bytes and manifests survive durable review; current plan, pause and control guards remain in force. Ordinary chat and unavailable-computer sessions do not acquire guest tool access.The local Pal sandbox adds authenticated, acknowledged bounded byte reads. Rebuild the local Pal image from this release to inspect saved images; an older worker fails explicitly before file contents are requested. Existing whole-file requests and generic worker defaults are unchanged.
The Pal image also includes the standard
fileutility for format checks. This reports file type; it does not establish application or visual quality. - —
2d5d18d: The Pal computer image is built in two stages.
local-computer/Dockerfilenow hasbase(the light computer: desktop, Chromium with the Namzu New Tab page, Terminal, Files, Text Editor and the guest worker, about 1.2 GB) andcreative(built FROMbase, adding Blender, GIMP, Inkscape, FreeCAD, KiCad and the other heavy applications). Building the Dockerfile without--targetstill produces the last stage, the same full image as before, so existing build commands are unaffected; pass--target basefor the light one. Each stage lists on the home page, and checks at build time, only the applications it installed (app-catalog.cjstags every application with its stage andprepare-home.cjs --stagerecords the built stage in/opt/namzu-computer/stage). The guest browser no longer shows a "Sign in to Chromium?" button (a managed policy turns browser sign-in, sync and promotions off), the Terminal tile on the home page uses the dock's terminal icon, and setuid and setgid bits are stripped from the image because nothing in the guest needs them.local-computer/build-image.mjsbuilds one stage with a chosen engine binary (docker,podmanorwslc), saves it, compresses it (zstd by default; xz, gzip or plain tar also load directly withwslc load -i) and writes amanifest.json(revision,stage,imageRef,imageId,protocol,file,sha256,tarSha256,sizeBytes,compressedBytes,format) that the Windows installer carries beside the archive.The default image reference of the provider is unchanged:
LOCAL_COMPUTER_IMAGEis stillnamzu-local-computer:1, the full image. The light base is taggednamzu-local-computer:2; a caller that wants it (the desktop app, from the image bundled in its installer) passesimage: 'namzu-local-computer:2'. Nothing is renamed or removed. - —
81d1721: The optional local computer image now includes 15 real application launchers, including Blender, FreeCAD, GIMP, Inkscape and LibreOffice Draw. Normal Chromium home and new tabs use the bundled Namzu New Tab page with a blank omnibox; navigated sites retain their normal address bar. Launchers use a guest-only native messaging host with a fixed extension origin and fixed application commands. The image activates its bundled New Tab extension and waits for its own startup tab to become ready, without rewriting browser policies or the saved profile. Application launches discard Chromium's disabled D-Bus session address. Kdenlive includes its video effects and uses a virtual audio driver so its editor can start without a host sound device.
Rebuild the optional local image and restart the owning computer to use the new applications and New Tab page. Existing running images are unchanged, and the persistent home volume and browser profile are retained. The bundled Godot is Debian's Godot 3; no unavailable reference apps are advertised.
- —
ce52b90: Add optional host-only
PalComputerScreenStream,PalEnvironmentLease.screenStreamandPalRuntime.computerScreenStream(palId, generation)for observing an exact current Pal computer over read-only RFB. Existing providers remain compatible; providers without the optional capability explicitly refuse live observation. Keep allocation authorization private in the embedding host and enforce read-only observation server-side; the stream does not grant operator input authority.The CLI exposes the owning desktop ACP stream method with geometry and generation rechecks, and reuses initialized registry roots to avoid repeated Windows ACL subprocesses while retaining fresh Pal definitions and directory identity checks. The local computer image adds x11vnc and authenticated binary WebSocket transport. Rebuild the installed image explicitly and restart the owning computer to enable live observation; older images retain screenshot/input behavior and advertise no stream capability. Persistent workspace and browser profile volumes survive.
The guest desktop includes a themed wallpaper, a real dock, Files, terminal and browser launchers, plus a local browser home page. Normal browser-window closure keeps the desktop alive; owned profile shutdown retains browser flush ordering.
- —
84a9472: Add optional exclusive operator control to local Pal computer leases. Hosts can transfer an idle guest desktop to human mouse/keyboard input while all agent sandbox and desktop mutation calls are fenced, then return control without starting a query. Pending work refuses transfer; unknown input or file-write outcomes require stopping the owned computer. A fresh agent screen capture is required after returning control. This local container boundary does not hide same-user guest files or credentials from arbitrary Pal commands.
- —
f7f48f9: Add
createLocalVirtualComputerProviderfor SDK Pals. Each Pal receives its own local Linux container desktop, visible Chromium browser, terminal command and file access, guest background processes with confirmed tree cancellation, and a persistent private volume. The provider requires an explicitly installed local Docker engine (the unchanged default) or an explicitly selected registered Windows WSL Podman machine, and a prebuilt Namzu computer image; it refuses remote engines and never installs, builds, or pulls images automatically. This is container isolation with a shared kernel, rather than a dedicated virtual machine. The Podman adapter verifies the local machine and pins owned loopback SSH forwards without changing machine settings or default connections; it has no per-container CPU, memory, or PID quota. Native Podman commands use a validated Windows profile directory even when an SDK caller starts in a WSL UNC directory. Custom command runners receive an optionalcwdand must honor it. Graceful lease shutdown flushes Chromium's persistent profile before removing the guest. Existing sandbox providers remain available. - —
4367edc: Add opt-in held keyboard input for a Pal's operator-controlled guest. Providers advertise
heldKeyboard: trueonly after negotiating the owned guest's support;PalComputerInputthen accepts session-scopedkey_down,key_upandrelease_keys. Providers must release their tracked held keys before confirming returned Pal authority. Existing completekeytaps and AIcomputer_useactions keep their behavior, and older local images do not advertise holds.The desktop captures a new keyboard lifetime for each focus session, forwards press and release events without replaying browser key repeats, and releases only that lifetime on focus/view loss. Cleanup retains exact Pal/generation and operator authority checks and cannot release another lifetime's keys. Rebuild the local Pal image to enable this capability; a runtime update alone retains the older image's tap input.
- —
ad2273f:
@namzu/sandbox/local-virtual-computer/setupgains the pieces that need Windows' permission:runElevatedWslcStepsturns on Virtual Machine Platform (and, on Windows 10, the WSL optional component) and installs the pinned WSL package in one elevated Windows PowerShell process, so a plan shows Windows' permission prompt at most once;PINNED_WSL_PACKAGEnames Microsoft's WSL 3.0.1.0 x64 package with its size and SHA-256;downloadWslPackagefetches it with resume, retry and a digest check;registerResumeAtSignInandclearResumeAtSignInask Windows to continue setup once at the next sign-in. Nothing existing changes. These functions did not work before this release (they threw "not implemented yet"), so no caller relies on an earlier behaviour. - —
f9f672b: Add the Windows Pal computer setup core under
@namzu/sandbox/local-virtual-computer/setup:inspectWslcReadiness(reads policy, Virtual Machine Platform, WSL and wslc versions and the loaded image without starting a virtual machine unlessprobeSessionis set),planWslcSetup,runWslcSetup(refuses a consent whose plan no longer matches the PC),loadWslcImage(checks the bundled archive's size and SHA-256 and the loaded image's id and protocol before recording it),stopIdleWslcSessionand the restart record. Nothing existing changes; the download of the WSL package, the elevated process and the sign-in resume entry arrive separately. - —
fc86c05: The local Pal computer provider accepts a third engine,
engine: 'wslc', for Windows' own container CLI, with an optionalwslcBinarypath. Nothing changes for Docker and Podman: their command lines are byte for byte as before, andwslcis never chosen unless you ask for it.wslc cannot take
--cap-drop,--security-opt,--read-onlyor--ipc, so a computer on this engine runs without those four controls; its boundary is the per-user virtual machine and the unprivileged guest user. Keep using Docker or Podman for a Pal that runs code you do not trust.LocalComputerSetupErroris now exported and carries an optionalcode(engine-missing,engine-too-old,virtualization-off,blocked-by-policy,image-missing,sdk-mismatch) so an application can react without parsing the message.EngineDialectandLocalComputerSetupCodeare exported as types. No existing export changes.Each readiness probe of a starting local Pal computer now times out after 3 seconds and is retried inside the startup deadline, so a published port that accepts a connection and never answers no longer stalls startup. On wslc, an image check that fails for a reason other than a missing image is reported as itself rather than as "not set up".
Patch Changes
- —
a5dbeb2: Remove Chromium's separate stock New Tab footer and its Customize Chromium button from the local Pal browser home. The guest-only launcher disables
NtpFooter; the native blank home omnibox, installed application launchers, normal website address bars and existing browser profile are preserved. The home header uses the same block-letter wordmark as the desktop and CLI, bundled as font-independent SVG geometry.Rebuild the installed local computer image and restart its owning computer to apply the change. Running containers keep their previous image; the persistent workspace and browser profile volume survives the restart.
- —
4367498: Setting up a Pal computer on Windows now names the step Windows is actually working on. While Windows asks for permission and turns Windows features on, the row reads "Turning on Windows features…" instead of "Installing Linux support…"; the next step is shown only once the elevated script reaches it.
runWslcSetupannounces just the first elevated step before the elevated call and reports every elevated step, in order, when the call ends, so a progress listener sees each step start and finish once.A setup step that Windows could not finish now has a Details line that says which step stopped and with which exit code ("Turning on Windows features stopped with exit code 1."), or that it did not finish when Windows gave no code, instead of repeating the main sentence. The
detailtext of afailedresult from an elevated step changes accordingly; match oncode,stepandexitCoderather than on the text.Namzu Desktop no longer says the same thing twice. A failed setup, a setup that needs an administrator and a setup waiting for a restart are toasted only when the Pal computer row is not on screen saying it already.
- —
42ef5e4: Harden the Windows Pal computer setup (
@namzu/sandbox/local-virtual-computer/setup). Every readiness probe now has its own time limit and a stuck one no longer hangsinspectWslcReadiness: it becomes an unknown fact, or aLocalComputerSetupErrorwith a plain message when the state cannot be decided. An unexpectedwslcfailure during a session probe is now that error instead ofimage-missing. A PC that has not restarted since Virtual Machine Platform was turned on readsrestart-pending(notvirtualization-off), and an unreadable restart record readsrestart-pending. The processor type comes from the PC, wslc is found underProgramW6432, and the image-load estimate is shorter.WslcSetupHostgainskeyExists; implement it if you pass a complete host (a partialhostoverride is unaffected). A failedrunWslcSetupresult may carryexitCode, and itsdetailno longer includes the number.LocalCommandErrorgains an optionalexitCode, and the provider'sprobe()returnssetupCodebesidereason. The engine-missing message now reads "Linux container support is not installed on this PC." (it no longer names wslc); match oncode, not on the text.A registry key that Windows would not let the setup read is no longer taken for one that is not there. That applies to the sign-in resume entry and to the pending-restart markers, so a PC that only needs a restart is no longer told its virtualization is off. An unreadable restart record now ends with the next restart instead of never. With a session probe, the pending-restart markers are not consulted and
0x80370102staysvirtualization-off. The Pal computer probe reports a setup code only for a setup failure.A registry key whose parent is also absent is now reported absent instead of unknown, so a PC with virtualization off and no Windows Update key reaches
virtualization-off. A policy read that times out is "could not check" rather than "not set". A barewslcexit is read as a missing image only for status 1. The leading "Windows " is dropped from the Linux container support messages (engine-missing, engine-too-old and the catch-all); match oncode, not on the text. - —
9d422c6:
runWslcSetupnow answers{ outcome: 'ready' }and records the image when the plan it was asked to run is gone only because the PC is already ready (the image was loaded before any record existed). It used to answerplan-changedand leave the record unwritten, so the nextinspectWslcReadinesswithout a session probe still saidimage-missing. A caller that treatedplan-changedas the only answer for that case now also seesready; any other changed plan still answersplan-changed. - —
eddf7aa: A Windows restart record that cannot be read and has no readable file date no longer makes the readiness inspection throw
LocalComputerSetupError. The inspection now reportsrestart-pendingwith norestartfield and no remaining steps. Callers that caught that error to show "delete restart.json" now receive a normal readiness result. - —
a2a4b17: In a Kubernetes terminal, keystrokes typed right after a resize now reach the program after the new window size does. Before, the resize was applied by a separate process that could finish after the input was written, so a command such as
stty size, or a full-screen program's first redraw, could report the old size, depending only on how busy the pod was. Input sent while no resize is pending is written immediately, as before.A resize that cannot be applied (the
sttyfails, or has not finished after 2 seconds) no longer holds typing back: the keystrokes queued behind it are written once it fails or the 2 seconds pass, so a stuck resize delays input by at most that long. - —
Updated dependencies [dd7ee4b]
- —
Updated dependencies [4314f90]
- —
Updated dependencies [b8852a1]
- —
Updated dependencies [7f5ac50]
- —
Updated dependencies [7f5ac50]
- —
Updated dependencies [56d88dc]
- —
Updated dependencies [3727e2b]
- —
Updated dependencies [1af4531]
- —
Updated dependencies [efb7b06]
- —
Updated dependencies [84452ec]
- —
Updated dependencies [53aed2a]
- —
Updated dependencies [6194bda]
- —
Updated dependencies [301f5b3]
- —
Updated dependencies [6194bda]
- —
Updated dependencies [56d88dc]
- —
Updated dependencies [f7f48f9]
- —
Updated dependencies [9ef6506]
- —
Updated dependencies [56e846c]
- —
Updated dependencies [2057fd6]
- —
Updated dependencies [c8613e0]
- —
Updated dependencies [6f46da3]
- —
Updated dependencies [be9f8b6]
- —
Updated dependencies [c1027fb]
- —
Updated dependencies [24cb6b4]
- —
Updated dependencies [b6ea5ca]
- —
Updated dependencies [bb0ad2e]
- —
Updated dependencies [d5030ca]
- —
Updated dependencies [7f5ac50]
- —
Updated dependencies [7473449]
- —
Updated dependencies [6194bda]
- —
Updated dependencies [6f46da3]
- —
Updated dependencies [f4b7d1d]
- —
Updated dependencies [6ff961f]
- —
Updated dependencies [023224a]
- —
Updated dependencies [56d88dc]
- —
Updated dependencies [7f5ac50]
- —
Updated dependencies [6f46da3]
- —
Updated dependencies [5e2fb3a]
- —
Updated dependencies [0f5aa96]
- —
Updated dependencies [84a9472]
- —
Updated dependencies [c1027fb]
- —
Updated dependencies [d8d562e]
- —
Updated dependencies [53aed2a]
- —
Updated dependencies [ce52b90]
- —
Updated dependencies [815433f]
- —
Updated dependencies [4367edc]
- —
Updated dependencies [6ff961f]
- —
Updated dependencies [6f46da3]
- —
Updated dependencies [f7f48f9]
- —
Updated dependencies [846557b]
- —
Updated dependencies [56d88dc]
- —
Updated dependencies [53aed2a]
- —
Updated dependencies [12b5af9]
- —
Updated dependencies [e447a47]
- —
Updated dependencies [093d657]
- —
Updated dependencies [57c99b2]
- —
Updated dependencies [c590142]
- —
Updated dependencies [1fa60b9]
- —
Updated dependencies [f0aeb9b]
- —
Updated dependencies [5a0a1f7]
- —
Updated dependencies [3727e2b]
- —
Updated dependencies [e447a47]
- —
Updated dependencies [56d88dc]
- —
Updated dependencies [98e8469]
- —
Updated dependencies [bebe135]
- —
Updated dependencies [f7f48f9]
- —@namzu/sdk@50.0.0
- —
Patch Changes
- —b7d83ec: Sandbox unit tests now limit worker parallelism on many-core hosts. Kubernetes transport tests also use a stable unreachable address for an unchanged pod and accept an acquire deadline that expires during the initial API request before readiness polling starts. Published runtime behavior is unchanged.
- —Updated dependencies [b7d83ec]
- —Updated dependencies [b7d83ec]
- —Updated dependencies [2b1cbb5]
- —Updated dependencies [b7d83ec]
- —Updated dependencies [b7d83ec]
- —Updated dependencies [b7d83ec]
- —Updated dependencies [b7d83ec]
- —Updated dependencies [b7d83ec]
- —Updated dependencies [b7d83ec]
- —@namzu/sdk@49.0.0
Patch Changes
- —25966a5: Workers started with
NAMZU_SANDBOX_PORT=0now report the actual bound port in their startup log, so callers can connect to the ephemeral listener.
- —25966a5: Workers started with
Patch Changes
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [fbeac55]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —Updated dependencies [567ada8]
- —@namzu/sdk@48.0.0
Patch Changes
- —Updated dependencies [28102e1]
- —Updated dependencies [82769f1]
- —Updated dependencies [82769f1]
- —Updated dependencies [82769f1]
- —Updated dependencies [82769f1]
- —Updated dependencies [28102e1]
- —Updated dependencies [28102e1]
- —Updated dependencies [28102e1]
- —Updated dependencies [28102e1]
- —Updated dependencies [28102e1]
- —Updated dependencies [28102e1]
- —Updated dependencies [82769f1]
- —Updated dependencies [28102e1]
- —@namzu/sdk@46.0.0
Patch Changes
- —Updated dependencies [2e2ea14]
- —Updated dependencies [2e2ea14]
- —@namzu/sdk@45.0.0
Major Changes
- —
3e7a97b: No API change. The peer dependency on
@namzu/sdkmoves to the 44 major, because the SDK itself had a major release (its run became a turn inside a session).@namzu/sandboxreads no renamed field, but its peer range is published as a caret on the SDK version it was built with, so the SDK major takes it out of range and forces this bump.What to do: upgrade
@namzu/sdkand@namzu/sandboxtogether. Nothing in your code changes.
Minor Changes
- —
0a89444: New: egress profiles.
defineEgressProfile({ name, hosts: [{ host, ports? }] })validates one named host allowlist, andcreateSandboxProvidertakes it asegressProfileinstead ofdefaultEgress. On docker, runsc and firecracker a profile becomesdeny-all(no hosts) or astaticallowlist. Whatever a backend cannot honour is refused at construction withSandboxEgressProfileError: ports on firecracker, any profile on the ACI standby pool or besidedefaultEgress, and abrokeredCredentialshost the profile does not allow. On docker and runsc a livesetNetworkPolicyunder a profile may only name hosts the profile covers. On kubernetes,createSandboxProviderrefusesegressProfile; putkubernetesEgressFromProfile(profile, { engine: 'cilium' })inbackend.egressinstead, which also bounds workspaces and writes the profile label only withprofileLabel: true.Nothing changes for a config without
egressProfile. - —
aee81a6: The docker and runsc egress proxy now enforces the
portsof an egress profile, on the port it actually dials: an upgradedhttp://host/and aCONNECTwith no port are checked on 443. A host may use the union of the ports of every rule that matches it. The same check is available onEgressProxyasallowedPorts, andegressPortsForRules(profile.hosts)builds that option from a profile's rules with the same union rule.Rebuild
egressProxyImagefrompackages/sandbox/egress-proxy/Dockerfilebefore usingports. A profile with ports sends the proxy a new configuration variable,NAMZU_EGRESS_PROXY_CONFIG_V2, and the backend first reads the image'sai.namzu.egress-proxy.configlabel withdocker image inspect, refusing an image that does not declare version 2 (or that is not on the daemon:docker image inspectdoes not pull). Profiles without ports, and every policy without a profile, need nothing: the proxy gets the same configuration and argv as before. - —
c366ca7: New: sandbox seeds.
ensureSandboxSeed(sandbox, seed, { root })makes the git repositories of adefineSandboxSeed({ name, repositories })present underrootinside any sandbox, throughexeconly. Every call checks each repository (origin URL, and that its pinned or recorded commit is an ancestor of HEAD; arefchanged since the clone, or a pin dropped, is drift unless the ref's commit is exactly HEAD) and clones only what is missing, so running it after each create or workspace resume costs one check when nothing changed. Drift is refused before anything is cloned (onDrift: 'report'records it instead), and nothing is ever deleted or re-cloned.rootis required; on docker, uselayout.scratch, not the outputs root. A repository directory may not sit inside another's, nor under.namzu/. URLs with a user name or password,ssh://andgit@host:pathare refused, so no credential enters the guest. The guest needssh,git,find,mkdir,mktemp,rmand GNUmv.Nothing changes for code that does not call it.
Patch Changes
- —251d815: On the docker backend, two overlapping
setNetworkPolicy()calls on one sandbox could leave no egress proxy running while a caller was told its policy was in force, or resolve one call while the other call's policy was the one applied. Calls on one sandbox now run one at a time, in the order they were made, and each resolves only once its own policy is running. A call that asks for the allowlist already in force no longer restarts the proxy. No action is needed. - —Updated dependencies [8805360]
- —Updated dependencies [9355755]
- —Updated dependencies [755a81a]
- —Updated dependencies [cb1f00c]
- —Updated dependencies [933ba6d]
- —Updated dependencies [3e7a97b]
- —Updated dependencies [a84dc1c]
- —Updated dependencies [b064cea]
- —Updated dependencies [9238347]
- —Updated dependencies [a14b013]
- —Updated dependencies [3641102]
- —@namzu/sdk@44.0.0
- —
Patch Changes
- —
006fdf3: A Kubernetes
openTerminalno longer depends on a kernel build option to find the terminal it just started: where/proc/<pid>/task/<pid>/childrendoes not exist, the guest agent discovers the tree by scanning/procfor the parent instead (#516, a follow-up to #512).What was wrong.
findPtySlavewalks/procfor the shell util-linuxscriptforked, and its only way in wasprocessChildrenreading/proc/<pid>/task/<pid>/children. That file isCONFIG_PROC_CHILDREN— not part of the kernel's base/procsupport — and the reporter's guest (a 6.6 mshv guest on a sandbox cluster) has no such entry at all:cat /proc/108/task/108/childrenanswersNo such file or directoryfor ascriptthat is alive with a shell under it, and the entry is simply absent from/proc/<pid>/task/<pid>/where it would sort betweencgroupandclear_refs. So the walk's queue was empty on every one of its 200 attempts,probePtySlavewas never called at all, and thetty_nrfallback #512 added — which is per pid the walk has already dequeued — was unreachable, because no pid was ever dequeued. The reported symptom is exactly what that reads as:terminal PTY slave did not appear (200 attempts, 1112ms; last children(): ENOENT; last fd/0: never read; last stat: never read; script: alive).What changed.
parseProcessStatnow also carries field 4,ppid, and one pass over/procgroups every live pid by its parent — the DIRECT-children relation the walk needs, from the same kernel, through a file (/proc/<pid>/stat) that is world-readable and gated on nothing. It is not the file's relation exactly, and the code says so rather than calling the two the same thing: field 4 is the parent's thread-group id, so the map is the thread-group-collapsed graph — WIDER than the file at a leader node, where a child forked by any thread of the group is listed under the leader and the file'schildrennames the leader's own children only, and EMPTY at a non-leader tid, a nodereaddir('/proc')never yields and this walk never asks about. Neither difference takes a candidate away: the file's listing at a leader is a subset of the scan's, every node this walk visits is a process rather than a thread, and what the scan can add is a child the file's own walk would have missed.findPtySlavetakes the file first and always, and reaches the scan only where a live pid answersENOENTfor it:ENOENTalone is ambiguous, and/proc/<pid>being present is what tells a kernel without the option from a process the kernel has released. A pid that is actually gone still ends the walk on the same observation it always did, and a refused read (EACCES,EMFILE) still takes the path it did before — no scan, and the errno reported as it was.The scan is discovery and nothing else: it cannot accept a candidate, only
probePtySlavedoes that, sofd/0→tty_nrorder, the pid that comes back with the slave (the shell's, which the session teardown rests on), the breadth-first order and the early stop are all unchanged. One/procpass is shared by every pid an attempt visits, so a walk over K candidates costs one pass per 5ms poll rather than K of them, and on any kernel that HAS the file — every guest this agent has been measured in — the scan never runs at all. The pass reads each pid'sstatsynchronously, which is the one reader in the agent that does: measured,readFileSyncanswers in ~12µs per pid where the promisified read costs ~276µs, so a sixty-process guest spends ~0.7ms per pass rather than ~15ms, and what the synchronous call costs is syscall time and nothing else — procfs builds that line in kernel memory, so there is no device behind it to block on. The listing that precedes the loop stays asynchronous, being one call per pass rather than one per pid.That synchronous read is also why the pass is bounded, not merely measured, and the bound is the reason this is worth a second look: the agent is single-threaded and serves every terminal, exec, stream and
healthzon that one event loop, and on the kernel family this fix exists for the scan runs on EVERY attempt — so a pass that never yielded would hold all of them for as long as the guest has processes, in one lump per attempt per terminal. The loop hands the loop back every 64 reads (setImmediate), which caps the uninterrupted stretch at 64 reads rather than at the size of the machine. Measured by driving the real function — extracted from the shipping file — over a/procholding a copy ofsleepper spawned process: the longest gap between two yields, which is the longest the loop went without a turn, was 0.97-1.05ms at 4041 live pids and 0.69-0.93ms at 1041, where the same passes with the yield removed held the loop for 40.1-42.9ms and 10.6-11.8ms. The pass pays for that in event-loop turns and nothing else: 41.7ms → 45.1ms at 4041 pids (median of five) for 62 turns, and at 41 pids no yield fires at all, the pass being shorter than the stretch it is allowed.Where the scan does run, the failure message now says so (
last children(): ENOENT, discovered by a /proc scan) instead of leavingENOENTto read as a broken walk, which is the reading that cost the reporter a cluster to correct. A scan whose ownreaddir('/proc')FAILED says that instead —last children(): ENOENT, and the /proc scan that stands in for it could not list /proc: EACCES— because a listing that never happened must not be reported as a scan that ran and came back empty, which would be the same misreading one level down.What is not proven. That the reporter's guest answers
readywith this agent. Not pinned by a test, and deliberately: a timing assertion on the yield would be flaky, and the suite has no seam for a denied/proclisting. Both are verified out of suite instead — the stall table above by measurement, the failed-listing message by drivingdiscoverChildrenwith a fakedfs— which is why those numbers are quoted here rather than asserted in the file. The absence cannot be produced here — this machine's kernel has the option, and the guest is a cluster — so the regression test arranges it the way this suite arranges what it cannot reproduce: a copy of the shipped agent with its onechildrenread pointed at a path that is not there, patched by anchored edits that assert each anchor occurs exactly once, run over a real loopback socket against real processes (packages/sandbox/src/backends/kubernetes/__tests__/terminal-pty-slave.test.ts). Against the pre-fix source those cases fail with the reporter's own line, field for field —terminal PTY slave did not appear (200 attempts, 1043ms; last children(): ENOENT; last fd/0: never read; last stat: never read; script: alive)— and against this one they answerready, resize the shell's real pty and end a SIGHUP-immune background job through the shell's session. The same file pins the cost decision from the other side, asserting with the shipped agent that a walk which CAN read the file never runs the scan at all — and the two cases that must scan assert the counter saw theirs, so a rename cannot turn that bound into a vacuous pass.Redeploy the guest image to get it.
agent/agent.cjsis baked into the guest images (k8s/DockerfileCOPYs it), andnpm pack @namzu/sandbox@18.1.0 --dry-runconfirms it is not in the published tarball — 0 entries. No public type, export, wire shape or documented default changes here, which is why this is a patch.
- —
Minor Changes
- —
ef570fe:
createSandboxProvidernow forwardsbrokeredCredentials, andContainerBackendConfigcarries the field.What this makes possible is the thing the container tier's egress boundary was built for. A host can now name credentials the sandbox never holds: the sandbox carries a placeholder, and the real value is stamped onto outbound requests at the egress proxy, so a prompt injection that exfiltrates the sandbox's environment does not carry the token with it. Until now the field could only be read from
DockerBackendInternalConfig, which is not part of this package's public surface, so brokering was reachable only by callingbuildDockerBackenddirectly — not by building a provider the documented way.Nothing existing changes behaviour. The field is optional and unset by default, and it is forwarded in the guarded style its neighbours use, so a config that does not set it produces a backend config with no such key at all rather than one with an explicit
undefined;egressProxyContainerConfigreads either as "no credentials to stamp". A provider built without the field starts exactly the proxy container it started before. The one caller whose behaviour does change is the one that was passing the field through a type cast: it now takes effect.Set it where it can be honoured, and not elsewhere. The field is on
ContainerBackendConfig— the docker and gVisor (runsc) tier — because that is the only tier with an egress proxy to stamp a credential at. The microVM tier hands its allowlist to its guest orchestrator and the kubernetes tier writesNetworkPolicy/CiliumNetworkPolicy; neither has a proxy, so the field is not on their configs. Within the container tier it is applied only where a proxy is actually started, which is astaticorresolverpolicy with anegressProxyImage:deny-allandallow-allrun no proxy, and credentials set beside either are never applied.Each entry names one host (
host, matched by the allowlist's own rules so.internal.examplecovers subdomains), the header to set, and the value. The value leaves this process on its way to the proxy container, so anything with access to the docker daemon can read it — treat daemon access as credential access. Seedocs/sdk/sandbox-egress.md.
Patch Changes
- —
a52727a: The Kubernetes/Firecracker guest agent now finds a terminal's PTY slave without
readlink('/proc/<pid>/fd/0'), which was the one probe it had and was answered by a kernel permission check it never declared (#512).What was wrong.
handleTerminalspawns util-linuxscript, then walks/procfor the shellscriptforked, looking for a candidate whose fd 0 readlinks to/dev/pts/<n>. That path is used for three things — thestty -Fresize, the pid whose kernel session a teardown signals, and never mistaking a pipe for a terminal — and the probe behind it isptrace_may_access, which refuses a reader whose credentials do not match the target's./proc/<pid>/fdisdr-x------owned by the target;/proc/<pid>/statis world readable.Measured in a real kind cluster: with the reader's uid differing from the target's,
readlinkfailsEACCESevery attempt for the whole one-second budget whilescriptis still alive, and the terminal answers{"type":"error","error":"terminal PTY slave did not appear"}— the reported symptom, reproduced byte for byte. With a root reader and a uid-1001 shell:fd/0 = <EACCES>while/proc/<pid>/statreads and names136:3 -> /dev/pts/3, which is exactly the slave root sees throughfd/0for the same live process.What changed.
readProcessStatnow also carries field 7,tty_nr, decoded with the kernel's ownnew_decode_dev(major 136 is devpts).findPtySlavestill asksfd/0FIRST and behaves identically wherever it can answer — the fallback is only reached when the readlink fails or answers with something that is not a PTY slave, and the walk still returns the first candidate in breadth-first order, so the pid it reports is the one the fd 0 probe alone returned whenever that probe can answer at all. It also stops as soon as/proc/<script>is gone rather than spending the remaining budget on a released pid — and a terminal whosescripthas already exited is answered with its ownexitframe instead of with this error, since the walk's failure there is a claim about a terminal that had already ended. The failure names what it could not read, and names only what it read:terminal PTY slave did not appear (200 attempts, 1002ms; last children(): EACCES; last fd/0: EACCES; last stat: EACCES; script: alive). Every field is the LAST OBSERVATION of the probe it names rather than the last error that probe ever had, andscript's own line is a third value —alive,gone, or the errno that stopped it being read, so a host is never toldscript aliveby a read that never happened. The leading phrase is unchanged, so anything matching on it still matches.What is not proven. Which condition in the reporter's cluster made the readlink fail for the whole budget. The uid-mismatch shape reproduced here looks impossible in the shipped image, where the agent,
scriptand the shell are all uid 1001, and no run of the unmodified agent in a kind cluster (task-pod or workspace-pod shape) reproduced the failure. This release removes the dependency and makes the failure legible; it does not claim to have found the trigger.Redeploy the guest image to get it.
agent/agent.cjsis baked into the guest images (k8s/Dockerfile,packages/sandbox/agent/), not shipped in the published tarball, so a deployment keeps the old agent until its image is rebuilt.agent.cjs,entrypoint.sh, theDockerfileand the worker are all guest/deployment files outside the published tarball: no public type, export, wire shape, or documented default changes here, which is why this is a patch.
- —
telemetry
2 releases
Major Changes
- —
3e7a97b: Requires
@namzu/sdk >=44.0.0(was>=1.0.0), and the span, metric and attribute names the kernel emits changed with it. Dashboards, alerts and queries that filter on the old names stop matching.- —
agentRunSpanName(from@namzu/telemetry/attributes) isagentTurnSpanName, and the span it names isnamzu.agent.turn <name>(wasnamzu.agent.run <name>). - —The histogram
namzu.run.durationisnamzu.turn.duration. - —The attributes
namzu.run.id,namzu.run.statusandnamzu.run.parent_id(NAMZU.RUN_ID,NAMZU.RUN_STATUS,NAMZU.RUN_PARENT_ID) are removed. Spans carrynamzu.turn.id(NAMZU.TURN_ID),namzu.turn.status(NAMZU.TURN_STATUS) and, on a child session,namzu.session.parent_id(NAMZU.SESSION_PARENT_ID). - —Every span the kernel opens for a turn — the turn's root span, each
namzu.agent.iterationspan, eachchat <model>span and eachnamzu.tool.executespan — now carriesgen_ai.conversation.id(GENAI.CONVERSATION_ID), set to the Namzu session id, andnamzu.turn.id, so one conversation's turns group together and any span finds its turn. A tool a host calls directly, outside a turn, gets them only when itsToolContextnames a session and a turn. - —Session export forwards the SDK's renamed events:
turn_*instead ofrun_*andchild_session_*instead ofsubsession_*, each withsessionIdand, inside a turn,turnIdinstead ofrunId.
What to do: upgrade both packages together, then replace
namzu.run.*withnamzu.turn.*(andnamzu.run.parent_idwithnamzu.session.parent_id) in every dashboard and query. - —
- —
Patch Changes
- —
8d5223b: Nothing a consumer installs or calls changes, and that is the whole of this release.
vitestmoves from^3.2.6to^4.1.11in thedevDependenciesof all nineteen packages that declared it, and@vitest/coverage-v8moves with it in@namzu/sdk. Every occurrence is a devDependency — checked, not assumed — sodependencies,peerDependencies, exports, types, defaults and the wire shape are untouched, and the published tarballs differ from the previous release only inpackage.json#devDependencies.The reason is a security fix with no 3.x backport.
GHSA-82fw-gwwq-j7x9("Path Traversal / Arbitrary File Read via@vitest/mockerRedirect Mock") coversvitestand@vitest/mockerfrom2.1.0up to4.1.11, so^3.2.6can only be resolved by leaving the 3.x line.4.1.11is the first patched release and is what the lockfile now resolves for both.What this costs anyone who works on the repository rather than with it: the upgrade was not a version bump. Vitest 4 changed test discovery, coverage configuration, mock construction and reporter output, and each of those broke something here that had to be migrated rather than worked around. Those fixes are all under
__tests__/,vitest.config.tsfiles andscripts/, none of which is published, which is why this is a patch and not a major.You do not need to do anything. If you pin
vitestyourself to run this project's own suites, note that the config files it ships are now written for>= 4.1.11and will not run under 3.x.
- —
zen
13 releases
Patch Changes
- —
d328c1b: Serve anonymous free-model calls from
https://opencode.ai/inference/v1instead of the configuredhttps://opencode.ai/zen/v1.The free-tier gateway answers
403 FreeTierError— "OpenCode's free tier can only be used from within OpenCode" — to anonymous traffic on/zen/v1, and does so however faithfully the request is shaped. Measured oncat-jam-freewith the OpenCode-shaped identity headers, the four injected tool names and a session and request id in the client's own format, both against the same session in the same minute: the same request is403on/zen/v1/chat/completionsand200on/inference/v1/chat/completions. The request id's random half is not what the gateway reads — flipping it, keeping the timestamp half fresh or stale, and re-serialising the body all leave the verdict unchanged — only the route decides.To upgrade and keep working, nothing is required: anonymous free-model calls move to
/inferenceby themselves, and every keyed call stays onZEN_BASE_URLexactly as before. A caller-ownedbaseURLproxy is likewise untouched, and only the chat route is reachable anonymously — the Anthropic and Responses routes answer401 Missing API keyon either host, as they did before. Verified live oncat-jam-free: a plain turn and abashtool round both complete.
- —
Patch Changes
- —
4a6ec17: Refuse to claim strict tool input generation where the wire would refuse the request.
editandwritesetenforceModelInput, and the Zen driver answered that withstrict: truefor both on the chat-class wires. The Zen free-tier gateway enforces the strict subset's structural rule itself — every object that declarespropertiesmust list all of them inrequired— and refuses the WHOLE request when one tool fails, soedit's optional fields (a find-and-replace OR an insertion) took every other tool in the call down with it.cat-jam-freeis the model that enforces; the symptom wasInvalid schema for function 'edit'.- —
@namzu/sdkgainsacceptsStrictSchema(schema), the wider question a driver asks before claimingstrict: trueon one request: the keyword deny-list thatfindStrictSchemaViolationsalready runs, plus the structural rules, plus a guard for a tool that declared no schema at all (the deny-list walks nothing inundefinedand finds nothing). It is additive;findStrictSchemaViolationsandassertStrictSchemakeep the registration-time job they had. - —
@namzu/zennow sendsstrict: acceptsStrictSchema(...)for a tool named inenforceToolInputSchema, sowritekeeps the guarantee andeditsendsstrict: false. Responses still sendsstrict: falsefor every tool, as it did. Nothing about a schema changes, and Namzu validates tool inputs before execution either way. - —
edit'sinsertLineunion branch{ const: 'end' }states notype, which the strict dialect has no spelling for; it is now{ type: 'string', const: 'end' }. The validator reports any union branch that never states a type.
What breaks. A caller that relied on Zen sending
strict: trueforeditloses constrained decoding for that one tool on the chat-class wires. To keep it, the schema would have to require every field and make the optional ones nullable, which changes whateditaccepts on every provider. - —
- —
Major Changes
- —
2395a85: Breaking:
fledge-alpha-freeleaves the bundled catalogue. Zen no longer documents or serves it, sofindZenModel('zen', 'fledge-alpha-free')andgetZenModels('zen')no longer return it, and a request naming it fails as an unknown model. Pick another free Zen model, for examplestep-5-preview-freeormimo-v2.6-flash-free. - —
4367edc: Remove the default 4096-token per-call output ceiling when
maxTokensis omitted. Requests now use the known model's advertised output ceiling instead of selecting a smaller service default by omitting the wire field. Unknown Chat, Responses and Google models without catalogue metadata leave the field omitted. Messages requiresmax_tokens, so an unknown Messages model without a known ceiling requires explicitmaxTokensor model catalogue metadata. Explicit limits remain unchanged and include manual thinking tokens on Messages.Calls that relied on the previous default can produce longer output and consume more tokens. Set
maxTokens: 4096explicitly, ormaxResponseTokens: 4096on SDK turns, to preserve the previous behavior. Service and model limits still apply. This fixes Pal requests where Space Bunny spent the 4096-token allowance on reasoning and stopped before using tools, even with unlimited SDK turn guards. - —
0de3571: Breaking: Zen Go's
space-bunny-freeleaves the bundled catalogue. Go no longer serves that id, sofindZenModel('go', 'space-bunny-free')andgetZenModels('go')no longer return it, and a Go request naming it fails as an unknown model. On Go, usespace-bunny, which the snapshot now carries. Zen's ownspace-bunny-freeis unchanged.Zen now carries models that the service routes, prices and serves but that models.dev has not described yet, where it used to omit them. These models get conservative limits: 65,536 context, 8,192 output, text input, tool use on and no effort levels. They also get a new optional
ZenModel.limitsVerified: false. Every other model leaves the field out, so existing code reads the same shape. Once models.dev publishes a model's limits, the entry uses them and the flag goes away. Tool use is assumed for these models, so one that cannot call tools fails when it runs. Models are still never carried when their price cannot be read or the service does not serve them, and a missing price never becomes zero.The free rule changed earlier without a changeset. Any Zen model priced at zero in the active catalogue is now offered without a key through the experimental request path, where previously only a fixed list of eight ids was. An id outside the old list used to be refused without a key; now it can be selected, though the gateway may still refuse it.
The bundled snapshot adds
mimo-v2.6-flash-free,exo-free,fledge-alpha-free,ling-3.1-flash-free,grok-4.7,gpt-6.1-sol,claude-sonnet-5-5andclaude-haiku-5-5on Zen. On Go it adds MiMo V2.6 Flash and Pro,claude-haiku-5-5andspace-bunny.parseZenCatalogueacceptslimitsVerified: falsein a stored copy and rejects any other value. An older@namzu/zenrejects any stored copy that contains the field and falls back to its bundled snapshot.
Minor Changes
- —134932b: The bundled catalogue adds
step-5-preview-freeon Zen and on Zen Go, which both services now document and serve. Nothing else changes.
Patch Changes
- —
4367edc: Deliver screenshot and saved-image tool results to Chat Completions models as real, attributed image content after the complete tool-result batch. Previously these results failed local history conversion, despite the driver's advertised image capability, stopping Space Bunny Pal work after its first screenshot.
Preserve original tool IDs, text and failure markers; leave the durable journal and operator messages unchanged. Images with persisted delivery omissions stay omitted, and text-only histories keep their previous request shape. The model must support image input; chat tool documents remain explicitly unsupported.
- —
01b926c: Keyless Zen requests work again. The free-tier gateway now refuses every model that does not carry OpenCode's client identity — including
space-bunny-free, which used to answer — and the driver was sending that identity only for a hand-pickedverifiedpath, so an anonymous call tobig-pickleand to the new free models returnedFreeTierError.Anonymous calls to the official Zen endpoint now all take the same path: an OpenCode
User-Agentat 1.18 or newer (resolved fromOPENCODE_UA, then an installedopencode --version, then a 1.18.35 floor), a descendingses_session id, the client and project headers, and the four tool namesbash,glob,grepandreadin the request body. The tool names are injected only where the caller registered none; a caller that has tools keeps its own set and its owntool_choice. Namzu's own attribution headers still travel on credentialed calls, on Zen Go, on a caller-ownedbaseURL, and on catalogue reads.Nothing in the public surface changes. Pass
OPENCODE_UAif you want the advertised version pinned; without it the driver probes an installed binary once per process and falls back.
- —
Minor Changes
- —f0505b5: Anonymous Zen users can now select all eight bundled free models instead of only Space Bunny. The seven previously blocked models use an experimental OpenCode-compatible request identity on the official Zen endpoint; Space Bunny keeps its verified direct request path, and keyed Zen and Zen Go requests are unchanged. Zen may still reject an experimental model or route, so a free catalogue entry is not a guarantee of service. The CLI labels this access as experimental and keeps Space Bunny as its default.
Major Changes
- —
29b142f: Zen Go no longer includes
glm-5.1,qwen3.6-plus, orqwen3.7-maxin its bundled supported model roster. The service still lists these IDs, but its current documentation provides no route or price for them. Applications relying on these IDs should select a documented model or, if they have independently verified the wire, configureZenGoProviderwith an explicitprotocol. The CLI no longer offers these reviewed omissions.The default anonymous Zen model changes from
muse-spark-1.3-contributor-freetospace-bunny-freein both the SDK and CLI. Callers that need the old choice must passmodel: 'muse-spark-1.3-contributor-free'explicitly and try credentialed Zen access, subject to gateway admission. The gateway currently rejects direct keyless Namzu requests to Muse and six other documented free models with HTTP 403; only Space Bunny Free completed a live keyless text and tool-continuation test. Anonymous model discovery now admits only that verified model while retaining the other free models for credentialed use.Zen and Zen Go now follow the reference client's route overrides for DeepSeek V4 Flash and MiniMax, handle Muse Spark reasoning continuation without replaying gateway-bound ciphertext, and read the current Zen Go price table when refreshing the catalogue. These keyed routes have wire-fixture coverage but were not live-tested with an account. The CLI distinguishes model listing from verified inference and explains the gateway's specific HTTP 403 free-tier refusal.
- —
Patch Changes
- —567ada8: Updated package examples and evaluation fixtures for the SDK's
ToolRegistryremoval. Browser, computer-use and LSP documentation now show toolsets; the evals usetoolsetswhen they call the kernel. Provider changes are test fixture migrations only, with no provider runtime API change. Consumers that pass tools to the SDK should usetoolsets: readonly Toolset[]and wrap their definitions withtoolset(source, definitions).
- —567ada8: Updated package examples and evaluation fixtures for the SDK's
Minor Changes
- —
ab9108b: Derive the Zen and Go model catalogue at run time, opt-in. The new
@namzu/zen/cataloguesubpath exportsfetchZenCatalogue(), which reads the same five sources the bundled snapshot is generated from (both documentation pages, models.dev and both/modelsanswers) with a deadline and byte limit per source, and returns a whole validated catalogue plus a report, or rejects. It never returns a partial list.buildZenCatalogue()does the same derivation from texts you fetched yourself, andparseZenCatalogue()re-admits a stored copy strictly. A derivation is held to the same checks as a stored copy, so a result is never one its own saved copy would fail to read back.Pass the result as the new
ZenConfig.catalogue, or a function returning it, and the provider checks it before the bundled snapshot for routing, anonymous admission, listing, context windows and effort levels. With a runtime catalogue, a served id that no source gives a wire format for is listed as<id> (no known wire format)and can be called only with an explicitprotocol; the bundled snapshot's entry for such an id, if it has one, is not used. A model name carrying a control, format or line-separator character is refused, in a derivation and in a stored copy.Nothing changes unless you opt in. Importing the package, constructing a provider and
getZenModels/findZenModelstill read the bundled snapshot and make no network request. Withoutcatalogue,listModels()returns what it did before.ZEN_OMITTED_MODELS(the reviewed omissions the snapshot was generated under) is now exported from@namzu/zenand@namzu/zen/models. The bundled roster is unchanged: 61 Zen and 28 Go models.
- —
Minor Changes
- —3e43fc2: The Go catalogue carries
grok-4.7on the Responses protocol, as the Go page routes it. Four ids that both pages route but models.dev does not describe yet —zen/grok-4.7,zen/mimo-v2.6-flash-free,go/mimo-v2.6-flashandgo/mimo-v2.6-pro— are not carried, because their limits cannot be derived. A caller that needs one names the protocol and limits itself.
Patch Changes
- —3641102: On the Messages protocol, a cache request now places block-level breakpoints —
after the static system text, and on the last message before request-only
context — instead of the request-level
cacheControloption, which the native adapter sends as a top-levelcache_control. That is Anthropic's automatic caching, which puts its breakpoint on the last block: the request-only context at the tail, which the next request replaces, so no later request read the cached history. Other protocols are unchanged.
- —3e43fc2: The Go catalogue carries
Minor Changes
- —
54429af:
muse-spark-1.3no longer advertises themaxreasoning effort.Upstream dropped it from the model's own menu, so a regeneration writes
['minimal', 'low', 'medium', 'high', 'xhigh']where it wrote six values. A caller that passeseffort: 'max'for this model is now refused with the refusal the driver has always given for an unadvertised level — "The selected model does not advertise this reasoning effort level." Pass a lower level for this model, or use a model whose menu still carriesmax.Why
minorrather thanmajor. The catalogue's own rule, in Zen and Zen Go, is that a removal ismajorbecause a carried id stops resolving; an addition or a repricing isminor. This model still resolves —getZenModels()andfindZenModel()return it unchanged, its id, protocol, prices, context window and tool support are identical, and the publicReasoningEffortunion in@namzu/sdkstill carriesmaxfor the models that do advertise it. The page's rule has two buckets, and this is the one that is not a removal of a carried id: an addition, a repricing, or any other change that leaves a carried id resolving isminor. Nothing was removed from the API and no default changed.No code changed. The only edit is the generated catalogue, regenerated rather than hand-edited:
scripts/generate-zen-models.mjsderives it from the live documents andmodels.dev, which is also what turned this into a red gate before anyone noticed by hand.
- —
Minor Changes
- —
7b3323f: The Zen catalogue gains two models. They arrive in the same release as a generator fix that had to come first: the Zen page had started publishing a route row this script refused to read, and while that row stood the catalogue could not be checked against upstream at all.
New on Zen, both selectable once your key admits them:
qwen3.8-flashon the Messages wire at $0.15/$0.47 per million tokens, anddeepseek-v4.1-flashon Chat Completions at $0.30/$1.20. Both were already carried on the Go service; upstream has since added them to the Zen route table as well, which is what made this roster move. Nothing was removed, no price changed, andgetZenModels,findZenModel,ZenModelandZenProtocolkeep their signatures and types, so the bump isminor.Two ids stay uncarried, and no longer stop the catalogue being checked. The Zen page routes
jev-1.13andjev-1.13-freeon an endpoint whose AI SDK package column carries a dash rather than a package: the page names the model, its id and its endpoint, and no source states a wire for it — models.dev holds a name and limits for both and no package either. That row used to stop the generator as a page that had changed shape, which is the defect this release fixes: a row stating no wire is now read as exactly that, and both ids are omitted by name insrc/models.review.jsoninstead. The service does serve both, so if you call one of them the wire is yours to state — there is none to carry them on, which is the whole reason they are omitted — and that needs a real credential, because anonymous admission is only claimed for models the catalogue carries. The page's free-model list namesjev-1.13-free; that is a statement upstream makes about it, not an anonymous access this driver grants.
- —
Patch Changes
- —
8d5223b: Nothing a consumer installs or calls changes, and that is the whole of this release.
vitestmoves from^3.2.6to^4.1.11in thedevDependenciesof all nineteen packages that declared it, and@vitest/coverage-v8moves with it in@namzu/sdk. Every occurrence is a devDependency — checked, not assumed — sodependencies,peerDependencies, exports, types, defaults and the wire shape are untouched, and the published tarballs differ from the previous release only inpackage.json#devDependencies.The reason is a security fix with no 3.x backport.
GHSA-82fw-gwwq-j7x9("Path Traversal / Arbitrary File Read via@vitest/mockerRedirect Mock") coversvitestand@vitest/mockerfrom2.1.0up to4.1.11, so^3.2.6can only be resolved by leaving the 3.x line.4.1.11is the first patched release and is what the lockfile now resolves for both.What this costs anyone who works on the repository rather than with it: the upgrade was not a version bump. Vitest 4 changed test discovery, coverage configuration, mock construction and reporter output, and each of those broke something here that had to be migrated rather than worked around. Those fixes are all under
__tests__/,vitest.config.tsfiles andscripts/, none of which is published, which is why this is a patch and not a major.You do not need to do anything. If you pin
vitestyourself to run this project's own suites, note that the config files it ships are now written for>= 4.1.11and will not run under 3.x.
- —
Minor Changes
- —
a38d843: The catalogue was refreshed from its sources. The roster is unchanged — 59 Zen and 27 Go — so nothing a caller routes today stops resolving.
gpt-5.6-solis repriced: input2.0→4.0, output10.0→20.0per million tokens. The page had carried a 50% discount through 2026-09-18 and now states the full tier. A consumer that estimates cost from the bundled prices sees the increase here rather than at the invoice.Two omissions were retired.
zen/union-alphaandgo/union-alphawere recorded as omissions on 2026-09-18 after models.dev deleted their entry — the catalogue derives limits, tool support and modalities from there and never invents them. Hours later the service stopped serving the id and both pages stopped routing it, so the omission no longer applied and the stale rule dropped it. Neither id was ever carried, so this changes no caller's roster; it is recorded because a decision inmodels.review.jsonis re-examined on every run rather than left standing.
- —
Major Changes
- —
565ffa7: The Zen catalogue tracks upstream again, and the roster it returns has moved. The incompatible part is one removal, not the refresh:
omen-alphais gone from the Go roster, so a caller that pins it by name now gets a refusal where it used to get a request.omen-alphais still served. The service answers it on Go exactly as it answers a model that is carried —401 AuthError "Missing API key", the same answerkimi-k3gives, where an id it does not know answers401 ModelError "Model no-such-model-xyz is not supported". What changed is that the Go page no longer ROUTES it, and a route is the one thing that cannot be derived: the service's own/modelsanswer carries an id and nothing about how to call it. So the catalogue stops claiming a wire for it. If you depend on it, you can keep calling it by supplying the wire yourself —new ZenGoProvider({ apiKey, model: 'omen-alpha', protocol: 'chat' })— and the driver will route it, asclient.tsdocuments for any id whose protocol the host states explicitly. That is the only way to keep it, and it is a statement about a model nothing upstream documents any more.One model is new, and selectable once your key admits it:
deepseek-v4.1-flashon Go (Chat Completions, $0.15/$0.60 per million off-peak).union-alphais not in this catalogue, and not for our reasons. It was added to the roster while this release was being prepared, and upstream removed it before the release shipped: models.dev — where the catalogue derives limits, tool support, modalities and effort options — deleted its entry on 2026-09-18, from every provider that carried it and not only from ours. Nothing about the service changed, and nothing here is invented to compensate: a model whose limits cannot be derived is omitted rather than carried with made-up numbers. You lose nothing by it. The published@namzu/zen1.0.2 does not carry this id either, and the roster this release ships is back to 86 models (59 Zen, 27 Go); if the free-model list in the docs made you expect it, that is the explanation. Both services still serve it and both pages still route it on the Messages wire, so you can call it the same way as any id whose wire the host supplies —new ZenProvider({ apiKey, model: 'union-alpha', protocol: 'messages' })— which needs a real credential, since anonymous admission is only claimed for models the catalogue carries. It comes back on its own, atminor, when models.dev restores the entry.Two Go prices are lower, because upstream repriced them:
deepseek-v4-flashanddeepseek-v4-flash-vision-expmove from $0.22/$0.66 to $0.15/$0.60 per million input/output. Reported cost and anything you setcostLimitUsdto are affected downward. These are estimates for SDK accounting, not invoices.Everything else is unchanged:
getZenModels,findZenModel,ZenModelandZenProtocolkeep their signatures and types, and every model that was already carried keeps its protocol.For future refreshes: additions and repricing are
minor, because a consumer's code keeps working. A refresh that DROPS an id ismajor, for the reason above — a pin on that name stops resolving — and it should always say which id and why, as this one does. The scheduled refresh derives that intent from the generator's own report rather than from anybody's judgement, and puts it in the pull request it opens, so a refresh cannot be merged as a change nobody is ever asked to release.src/models.tsis now generated bynode scripts/generate-zen-models.mjsfrom the two service documentation pages, from models.dev, and from the two services' own/modelsanswers, rather than a snapshot pinned to a revision.src/models.review.jsonrecords the curation decisions, and the CI gate Zen catalogue matches its source fails, naming the model, whenever a service documents OR SERVES one the catalogue neither carries nor omits. That second half is why the roster moved: the service answers 71 ids on Zen and 38 on Go, and twelve of them — three on Zen, nine on Go, the freedeepseek-v4-flash-freeandmuse-spark-1.2-contributor-freeamong them — are served while appearing on no page. None of the twelve is carried, because a model's wire is stated on a page and the/modelsanswer carries an id and nothing else; each is now omitted by name with a reason, solistModels()dropping it is a recorded decision instead of a model nobody noticed. The gate also reads those two answers live, so a source it cannot reach is reported as a failure rather than passing quietly — the check runs in CI and needs the network toopencode.ai, which it has.The catalogue does not update itself, and nothing here says it does. A refresh is one command: it fetches, rewrites the module if the roster moved, and prints what it added, removed and repriced. What is new is that staleness is loud and the refresh is proposed: the scheduled
.github/workflows/zen-catalogue-refresh.ymlruns that command daily and opens a pull request when upstream has moved, carrying the generator's own report and the list of served ids that need curating. A person reviews and merges it — nothing is written tomainby the job, and nothing is curated by it.Unchanged, and worth knowing before you file it as a regression: the service currently refuses anonymous use of every free model — including
muse-spark-1.3-contributor-free, which an earlier release verified working — with HTTP 403FreeTierError, "OpenCode's free tier can only be used from within OpenCode". That is upstream admission policy and predates this release; the catalogue still records the free models the pages publish, and the providers documentation now says so with the date and the evidence.
- —