Release notes
Release notes
Section titled “Release notes”0.23.34 — Native tools can confirm a failed mutation (#402)
Section titled “0.23.34 — Native tools can confirm a failed mutation (#402)”ToolExecutionResult accepts an optional failure_confirmed: bool = False. A
ledgered native tool that returns succeeded=False, failure_confirmed=True
settles failed (mutation_failed) instead of outcome_unknown; set it only
after verifying the write had no effect. Failures without the flag, plain-string
results and handler exceptions are unchanged. failure_confirmed=True with
succeeded=True raises ValueError. See the
skills reference.
0.23.33 — Approved-write execution start event (#397)
Section titled “0.23.33 — Approved-write execution start event (#397)”Approval-v2 streams emit tool_execution_started once after approval_resolved
for a human-approved call, before post-approval admission, mutation
checkpointing or transport work. It uses the existing v2 envelope and identities
and means local execution started, not remote commitment. tool_started,
tool_completed and terminal tool_execution keep their meaning. Post-approval
admission and storage waits honor cancellation and the effective request
ceiling: unsent calls report not_executed, dispatched ambiguous failures stay
outcome_unknown and are never retried. Tool budgets are not extended. See the
approved execution stream contract and its producer
fixtures.
0.23.33 — Preserve observability queries in presentation copies (#399)
Section titled “0.23.33 — Preserve observability queries in presentation copies (#399)”Receipts, approval requests and API tool events now show query fields,
including PromQL and nested log/trace pipeline stages, with secret-like
substrings scrubbed; they were previously fully redacted. Keys containing a
whole sql or script token stay opaque; incidental names such as javascript
no longer are. Secret-field redaction and the 2,500-character argument cap are
unchanged. Hosts that relied on tool_started hiding queries should read the
skills reference.
0.23.33 — Forward MCP resource links (#395)
Section titled “0.23.33 — Forward MCP resource links (#395)”tool_completed events forward well-formed _meta.links {label, url} pairs
from MCP results, alongside reference_url, including on error results.
Malformed entries and oversized strings are dropped individually; tool outcomes
and adapter signatures are unchanged. Hosts validate URL suitability; see the
skills reference.
0.23.32 — Persist finalizer coverage (#384)
Section titled “0.23.32 — Persist finalizer coverage (#384)”Session history now saves the trusted turn finalizer’s coverage, including
limitations, for model answers, recovered narration failures and deadline
wrap-up. Saved coverage matches TurnResult, API run_completed events and
coalesced results, as it already did for locally handled turns. Evidence
receipts, cancellation and erasure fencing retain their existing behavior.
Public entry coverage is verified across failed recovery, wrap-up, concurrent
sessions and erasure controls (#386).
0.23.32 — Preserve observed MCP reference URLs (#385)
Section titled “0.23.32 — Preserve observed MCP reference URLs (#385)”Public tool results keep producer-supplied reference_url metadata when present
and well-formed. Missing, malformed or oversized values stay absent. Hosts that
already ignore the field retain existing behavior.
0.23.31 — Bounded approved MCP execution (#375)
Section titled “0.23.31 — Bounded approved MCP execution (#375)”Hosts can register ApprovedMutationBudget to give selected, freshly human-approved
MCP writes a finite execution allowance within a fixed request ceiling. Session,
approval, transport, cancellation drain, coalescing and wrap-up share that bound.
Unconfigured hosts retain existing timing defaults; auto-approved calls receive
no additional allowance. See the host API contract.
MCPExecutionTimeout distinguishes pre-dispatch rejection from dispatched
uncertainty. mutation_outcome/v1 adds an optional timeout reason; unknown
dispatched writes remain blocked from replay until authoritative reconciliation.
API, Slack, CLI and Telegram approval waits honor an explicit request expiry.
0.23.31 — Completed evidence survives narration failure (#374)
Section titled “0.23.31 — Completed evidence survives narration failure (#374)”After a successful non-setup tool call, known nonretryable provider narration
failures now produce a bounded deterministic partial answer. This applies to
post-tool synthesis, recovery synthesis, summary nudges, truncated-response
recovery, and deadline wrap-up. No additional provider retry is made for a
nonretryable error. The answer retains tool results, evidence receipts, coverage,
degraded="deterministic_fallback", and time_boxed when the deadline expired.
The registered trusted turn finalizer runs before persistence and delivery, including coalesced callers. Loading a skill, asking a human, paging an earlier result, or a failed read alone does not qualify for this new recovery path. Authentication, permission, unknown provider failures, and failures before successful evidence remain errors on this path. Internal and persistence errors are not converted to completed turns. Explicit cancellation now propagates as cancellation instead of becoming a retryable narration timeout.
Unrecovered API runs retain the fixed agent run failed message and add a
content-free failure diagnostic object to the terminal event and warning log.
See Terminal answers and failures
for the field contract.
0.23.30 — Recover timeouts before the first investigation read (#373)
Section titled “0.23.30 — Recover timeouts before the first investigation read (#373)”After setup-only operations such as load_skill (including a failed load), a
retryable provider timeout can now recover with tools enabled once per turn.
The retry retains the setup acknowledgment and uses at most the smaller of the
original deadline’s remaining time and engine.session.wrapup_timeout_seconds.
It does not extend the transport deadline or retry a cancelled turn. A successful
recovery can dispatch the first read through normal approval and budget checks.
If recovery fails, the retry has already been used, or the deadline expires
before investigation starts, the terminal response says that the investigation
did not start. Setup-only deadline expiry does not make an additional synthesis
call. load_skill, human__ask, and read_tool_result are excluded from new
investigation findings and attempted-check counts; skill prompts are not evidence.
After any non-setup tool attempt, recovery remains synthesis-only: attempted reads and writes, including outcome-unknown mutations, are never replayed by provider recovery. Existing approval outcomes and tool/result pairing are retained.
Hosts adopting this fix (including last9/ai#929) need no configuration or wire
changes. Successful setup recovery returns the normal result (degraded=null);
terminal fallback uses degraded=deterministic_fallback, with time_boxed=true
when the original deadline expired. Provider-recovery telemetry distinguishes
stage=setup from the existing stage=synthesis.
0.23.30 — MCP servers that refuse the startup connect recover (#370)
Section titled “0.23.30 — MCP servers that refuse the startup connect recover (#370)”MCPManager.start() retries every started server that has no tools in the
background (1s doubling to 30s, jittered) until it connects or stop() runs, logging
ERROR while it has none and INFO once on recovery. is_healthy(), and so /healthz,
now fails while any started server has no healthy session or an empty tool list,
so a rollout does not mark such a pod Ready. Hosts whose servers legitimately
expose no tools will see /healthz return 503.
ToolRouter.lock_allowlist() admits the tools of a server that was toolless at
lock time once, on its first discovery; tools other servers added after the lock
stay outside it. Engine dispatch calls the new ToolRouter.discover_surface(),
which makes one connect attempt via the new MCPManager.connect_if_toolless(server),
bounded by 5s, the turn deadline and its cancellation, before a miss on such a server
becomes Unknown tool. It returns the snapshot the tool was found in, and approval
binds to that snapshot’s tool revision and definition; a tool published after the
lookup is not executed by that call. get_definition() stays a local lookup.
0.23.30 — Bedrock in-turn calls read the previous call’s cache (#364)
Section titled “0.23.30 — Bedrock in-turn calls read the previous call’s cache (#364)”Converse requests with prompt caching now also put a cachePoint on the message
before the last assistant reply, which is where the previous call of a tool loop
ended. Bedrock finds a cached prefix only at a cachePoint or within about 20
content blocks before one, so after a round of many parallel tools the next call
used to write the whole prefix again. A request still carries at most four
cachePoints: when the loaded-skills section takes the fourth, the tool list gets
none, as the system cachePoints already cover it.
0.23.29 — Opt-in Bedrock heartbeat cache TTL (#305)
Section titled “0.23.29 — Opt-in Bedrock heartbeat cache TTL (#305)”llm.bedrock.heartbeat_cache_ttl (5m or 1h, unset by default) adds that
ttl to every Converse cachePoint on heartbeat calls. Unset, requests are
unchanged: no ttl field, the default 5-minute checkpoint. BedrockProvider
takes it as the keyword argument heartbeat_cache_ttl; other values raise
ValueError at construction.
The bedrock extra now requires boto3>=1.42.31 (was >=1.37.24): older
botocore rejects the ttl field client-side with Unknown parameter ... cachePoint: "ttl". Hosts that pin boto3 below 1.42.31 must upgrade before
setting heartbeat_cache_ttl.
0.23.28 — Forced tools on streams; deadline-cut input from blocking calls (#360)
Section titled “0.23.28 — Forced tools on streams; deadline-cut input from blocking calls (#360)”create_message_stream accepts required_tool
Section titled “create_message_stream accepts required_tool”LLMProvider.create_message_stream(required_tool=...) forces one call of the
named tool, exactly as create_message does: Anthropic sends
tool_choice {type: tool}, OpenAI tool_choice {type: function}, and Bedrock
(through the base fallback) toolChoice.tool. An empty name or one that does
not match exactly one tool raises ValueError before any network call. The base
fallback forwards required_tool only when it is set, so host providers whose
create_message predates the parameter keep working.
Blocking calls keep deadline-cut tool input
Section titled “Blocking calls keep deadline-cut tool input”Anthropic and OpenAI (Responses, including Bedrock Mantle) now run a guarded
blocking create_message (one with a deadline or cancel event) over the SDK
stream. When the guard cuts it mid tool call, TurnResult.interrupted_tool_inputs
carries the input streamed so far, as it already did for streaming calls (#354),
so adapters without supports_streaming get it too. Responses, usage, cost and
span attributes are unchanged. Unguarded calls still use the non-streaming
endpoint. Bedrock Converse does not stream, so its field stays ().
0.23.27 — Per-turn session deadline hook (#355)
Section titled “0.23.27 — Per-turn session deadline hook (#355)”Hosts can set the turn deadline per request
Section titled “Hosts can set the turn deadline per request”Engine.register_turn_deadline(resolver) is called once per request as
resolver(message, adapter), before coalescing. It returns positive, finite
seconds, or None to use the configured engine.session.deadline_seconds.
Invalid values (0, negative, NaN, inf, bool, str) are rejected before any model
call. Only one resolver can be registered, and a skill whose bind() fails has
its resolver rolled back. The resolved deadline drives the turn, the recovery
call and the coalescing TTL and attach wait; the wrap-up budget is unchanged.
In-flight owners are judged stale by the TTL they started with, so a
default-deadline request no longer duplicates a still-running longer turn.
Hosts that register no resolver see no change.
0.23.27 — Deadline-cut streaming tool input (#354)
Section titled “0.23.27 — Deadline-cut streaming tool input (#354)”A tool call cut by the deadline keeps its streamed input
Section titled “A tool call cut by the deadline keeps its streamed input”When the turn deadline or cancel guard cuts a streaming model call while it is
emitting a tool call, the input streamed so far is returned as
TurnResult.interrupted_tool_inputs instead of being lost. The cut call is never
dispatched. Anthropic and OpenAI (Responses) streams report tool-input deltas
through the new StreamEmitter.
Limits: non-streaming calls cannot produce partial input, so the field is ()
for adapters without supports_streaming (including the API wait=true path
without stream=true) and for Bedrock Converse, which does not stream.
0.23.26 — Slack runtime readiness (#334)
Section titled “0.23.26 — Slack runtime readiness (#334)”Startup membership waits for Slack readiness
Section titled “Startup membership waits for Slack readiness”New bearer-authenticated GET /api/runtime-readiness returns the loaded workspace,
agent, connection, revision and generation once the Slack adapter is initialized,
and 503 before. Hosts must invite the bot only after all five fields match the launch.
Startup and queued join-event onboarding now wait for the same readiness, and
managed /slack/events returns 503 until it. See
runtime readiness.
Unreleased — Interrupted tool input (#353)
Section titled “Unreleased — Interrupted tool input (#353)”Time-boxed turns keep the tool input the cut call had streamed
Section titled “Time-boxed turns keep the tool input the cut call had streamed”When the session deadline cuts a streaming model call, TurnResult.interrupted_tool_inputs
holds one {"name", "input_json"} per tool_use block the call had started, in stream
order. input_json is the raw input received so far and is usually truncated JSON. The
engine never dispatches these calls; the time-boxed wrap-up is unchanged. Every other
turn returns (), as do turns whose cut call was not streamed: adapters without
supports_streaming (including API wait=true without stream=true) and Bedrock
Converse, which has no streaming path. Hosts read the field in a turn finalizer or from
handle_with_results.
GuardedAsyncStream passes a StreamEmitter
Section titled “GuardedAsyncStream passes a StreamEmitter”Custom providers built on GuardedAsyncStream now receive a StreamEmitter instead of a
text callback: replace emit(chunk) with emit.text(chunk), and report tool_use input
with emit.tool_input(block, delta, name=...) to support interrupted_tool_inputs.
0.23.26 — Mutation outcome history and live events (#341)
Section titled “0.23.26 — Mutation outcome history and live events (#341)”Mutation outcomes are readable after the fact
Section titled “Mutation outcomes are readable after the fact”Settled guarded mutations are now projected from the logical-turn ledger as a
mutation_outcome/v1 record: {schema, logical_turn_id, mutation_id, tool, status, resource_id, url, title}. Nothing new is stored; every view is derived
from the existing ledger entries, so history, live events and replay agree.
- History:
SessionManager.mutation_outcomes(key)and the authenticatedGET /api/mutation-outcomes?session_id=. - Live events: after a settlement is durable the engine calls
Adapter.on_mutation_outcome(outcome, mutation_revision=n); the API adapter emits amutation_outcomerun event, andGET /api/mutation-eventsreplays them over SSE withid:/Last-Event-ID. - Replay cursor: each logical turn keeps a durable
revisioncounter. Replay returns entries changed after the cursor, and an outcome that is stilloutcome_unknownis re-sent until it settles or is reconciled.
Hosts that do not read these views see no behaviour change.
0.23.24 — Native skill surfaces (#344)
Section titled “0.23.24 — Native skill surfaces (#344)”MCP tools withdraw while their server is unavailable
Section titled “MCP tools withdraw while their server is unavailable”This applies to every MCP host, including hosts without a resolve_surface hook.
Quarantine, transport cleanup failure, degraded sessions and reconnect withdraw
that server’s direct mcp__* tools and aliases from new model tool lists; earlier
versions kept the cached definitions while disconnected. Successful rediscovery
restores eligible tools. In-flight calls to an unchanged tool on another server
remain admissible. See skills reference.
The startup allowlist is an upper bound
Section titled “The startup allowlist is an upper bound”lock_allowlist() freezes the allowed names permanently, including when called
again. A startup tool can be withdrawn and later restored; a tool first discovered
after startup cannot enter the locked set.
0.23.24 — Durable mutation recovery (#342)
Section titled “0.23.24 — Durable mutation recovery (#342)”Guarded mutations refuse exact replay by default
Section titled “Guarded mutations refuse exact replay by default”Calls that require approval, and tools registered with register_mutation_tools,
now checkpoint intent before dispatch and their outcome before narration. An
exact replay within the same delivery or logical turn returns not_executed with
mutation_completed, mutation_failed or mutation_outcome_unknown instead of
executing again, including after a restart. Prepared MCP calls are checkpointed
after approval, so preparation and approval may repeat but the write is admitted
once.
New refusal reasons: mutation_arguments_changed (approved arguments changed
before dispatch) and mutation_checkpoint_unavailable (state storage failed).
Host error handling and eval assertions should recognize them.
Delivery identity and warnings
Section titled “Delivery identity and warnings”Live-request coalescing now honors explicit delivery IDs: distinct deliveries each reach durable admission; identical deliveries still coalesce. MCP tools that execute without approval, registration or a canonical read-only declaration log a warning (without arguments). See durable mutation recovery for the host contract.
0.23.24 — MCP approval preparation (#343)
Section titled “0.23.24 — MCP approval preparation (#343)”Preparation runs before approval by default
Section titled “Preparation runs before approval by default”All hosts now run the owning skill’s before_mcp_call hook before human approval,
including hooks that return no preview. This is a default behavior change with no
opt-in flag: read-only probes run even if the human later denies the call or the
approval wait times out. Prerequisite and correction decisions short-circuit
before the parent is approved; prerequisite reads follow their own policy, and
resumed parents require fresh preparation and approval.
Audit existing hooks before upgrading: keep probes read-only, honor cancellation and deadlines, and do not rely on prior human approval. Successful preparation is bound to the arguments and preview by a single-use receipt and is not repeated after approval. Existing approval overrides and auto-approval policies still apply.
Ordinary unguarded turns now also suppress native and MCP dispatch when their cancellation event is set. Previously this dispatch check applied only to guarded turns. Cancellation does not roll back work already executed.
Hook timeout reason changes
Section titled “Hook timeout reason changes”Hook execution/admission timeouts now return preflight_timeout instead of
preflight_failed. Update host error handling and eval assertions that match the
old reason to recognize preflight_timeout; other hook failures still use
preflight_failed. Request deadline expiry and cancellation remain distinct
deadline and cancelled outcomes.
Reserved argument and API presentation shapes
Section titled “Reserved argument and API presentation shapes”A top-level executable MCP argument named approval_preview is now rejected with
reserved_approval_preview, including legitimate server-defined arguments. Rename
that argument before using the tool with mithai.
API approval events keep flat tool_input arguments when no preview exists
(e.g. {"title": "Services"}). When a preview exists, even an empty object,
tool_input becomes {"arguments": {"title": "Services"}, "approval_preview": {}}.
Both shapes contain redacted presentation data; clients must support both.
Slack labels the evidence “Host-supplied preview”: mithai binds the evidence to
the call but does not verify its claims. Hosts must accurately describe the
arguments being applied.
See the skills reference for the preparation contract.