All notable changes to the Anda project will be documented in this file.
-
SkillManager::set_skill_filterβ A host can now install aSkillFilterpredicate deciding which skills on disk the manager may hold. Rejecting a skill drops it everywhere at once: not loaded, not callable, absent from the resident catalog in the tool description, and reported as not found by the reader tool β so an application-level enable/disable switch cannot leave a hidden skill reachable by name. The predicate runs duringloadbefore duplicate resolution, so rejecting the copy in a higher-priority directory promotes the next directory's copy instead of dropping the name. Installing a filter also prunes what it rejects immediately, so the registry is never inconsistent with the policy while waiting for a reload. This exists so an embedding application does not have to keep a second skill registry of its own, which would inevitably disagree with this one about what is dispatchable. -
openai-responseprovider family βModelConfiggains a newfamilythat always routes through the OpenAI Responses API (completion_model_v2) with streaming and effort support, where the existingopenaifamily decides by model name (gpt*β Responses API, otherwise Chat Completions). A config that must pin the Responses API no longer depends on agpt-prefixed model name.
- Reading a shadowed skill by name no longer fails as ambiguous β
skills_managerresolved a name present in several configured skill directories by refusing it, whileloadhad already resolved the same collision by directory priority. A personal skill shadowing a bundled one was therefore loaded and callable but unreadable, which under the inline execution default means unusable. The read path now applies the same priority rule; an ambiguity within one directory still errors, since there is no priority there to break the tie.
This release folds in the entries previously staged as 0.14.6, which was
never published, and adds a deep-module restructuring of anda_engine and
anda_core guided by a full design review (kept as an internal working
document, not part of the repository). Behavior is preserved across the
restructuring except where noted below. Every workspace crate moves to
0.15.0 because the restructuring changes public API in anda_core and
anda_engine.
- MCP 2026-07-28 β The MCP host now speaks the stateless revision alongside the older
initialize-based ones.McpServerConfig::lifecyclepicks how a server is approached:auto(default) probesserver/discoverand falls back to the legacy handshake β including on a fresh transport, since a pre-2026 server often drops the connection rather than answering "method not found" β whilediscoverandinitializepin one lifecycle.2026-07-28is only negotiated through discovery, so a server that merely echoes a proposed version cannot pull the host onto a revision it does not implement. Discovery metadata (title, description,instructions) feeds the same tool groups the handshake used to. - Subscription-based tool updates β SEP-2575 removed unsolicited server pushes, so for
2026-07-28peers that advertisetools.listChangedthe provider opens asubscriptions/listenstream per session and drains it in the background. Streams are not resumable: an ended stream is reopened while the transport is up, and the session is marked dirty across the gap so a change announced while nothing was listening still triggers a re-list. - MRTR and tasks handling β A
tools/callno longer always returns a result. Aninput_requiredround (SEP-2322) carrying onlyrequestStateis echoed back and the call continues; one that actually asks for sampling, elicitation, or roots β none of which this host advertises β comes back as a failed tool result, so the model can pick another path instead of losing the turn. The SEP-2663 tasks extension is opt-in per server throughMcpServerConfig::tasks: the provider then pollstasks/getat the server's suggested interval (clamped to 250 msβ10 s) up tomax_wait_secs, and cancels any task it walks away from. - MCP re-authorization β
complete_authorizationnow drops the server's live session after persisting the new grant, so re-running the OAuth flow (e.g. for updated scopes) takes effect on the next call instead of waiting for the old session to die. Newdisconnect_serverdrops a session while keeping the server and its routes β holding the per-server connect lock, so a reconnect already in flight cannot reinstate the retired credentials; newclear_credentialsalso deletes the persisted grant, forcingMcpAuthorizationRequiredand a fresh consent. A grant the authorization server has revoked now also surfaces asMcpAuthorizationRequiredinstead of an opaque transport error, so applications learn to re-run the flow. The headless/SSH flow (present the URL as text, paste the redirect URL back) is now documented inMCP_INTEGRATION.md. - Skill execution modes β Skills now run inline by default: the
skills_managertool returns the full SKILL.md and the calling agent follows it in its own context, keeping the conversation, the user, and the turn's resources in reach. A skill can opt into isolated execution withexecution: subagent(ormetadata.execution: subagent) in its frontmatter, exposing it as anSA_<agent_name>worker for long-running, parallelisable, or context-hungry procedures. The tool response now reportsexecution, acallablename for subagent skills, andbase_dirfor resolving bundled files. Skills that previously ran as subagents must declare the execution mode to keep that behavior. resource-tagsfor skill subagents β New frontmatter field narrows which offered resources a delegated skill receives; when absent it accepts every offered resource so the current turn's attachments reach it.model::testing::ScriptedCompleterβ A programmable completion double (queued replies, closures, error injection, request recording, echo fallback) available to downstream crates, replacing the need to hand-roll a fake provider per test.subagent::ConversationRecordsβ The two-method persistence port the subagent conversation recorder actually needs (create/update).memory::Conversationsis its AndaDB adapter; AndaDB field encoding no longer crosses into the subagent layer, andSubAgentConversationRecorder::with_storeaccepts custom implementations, so subagent persistence is testable without AndaDB.context::DiscoveredToolsβ Discovery-tool policy (observation, merge probing, output compaction) extracted from the completion runner into the module that owns the discovery vocabulary.BaseCtx::path()β Public accessor for the context namespace path (a_<agent>for an agent call,t_<tool>for a tool call). Tool hooks are keyed by argument/output types, so two tools with identicalArgs/Outputshare one hook slot; this is how a hook identifies which one invoked it.
- Registries own their invariant β The
setmap onToolSet,AgentSet, andToolProviderSetis now private, so the lowercase-key invariant can no longer be bypassed by direct mutation. The replacement interface:add_dyn(validated insert of a type-erased entry),iter()((lowercase_name, entry)pairs in name order), and ownedIntoIteratoryielding entries in name order.EngineBuilder::register_tools/register_tool_providers/register_agentsnow merge throughadd_dyninstead of re-implementing the duplicate check. CacheFeatures::cache_raw_iterremoved β It leaked the runtime's internal cache entry representation ((Arc<String>, Arc<(Bytes, Option<CacheExpiry>)>)) into the capability trait and had no production consumers. Context implementations simply drop the method; the engine keeps an equivalent test-only iterator on its internalCacheService.- Dead text-decoding entry points removed β
text_fromandutf8_text_from(unused owned-Vecvariants) are gone, andutf8_text_from_bytesis now private (it equalstext_from_bytes_with_encoding(data, None)). Every entry point the engine actually calls is untouched:text_from_bytes_with_encoding,text_encoding_for_label,text_encoding_label,platform_text_encoding, andwindows_code_page_encoding.text_from_bytesis also retained β it has no in-tree caller, but it is the only ergonomic platform-default entry point (text_from_bytes_with_encoding(data, platform_text_encoding())) and is plausibly used downstream, so it was kept rather than widening the break.
modelsplit into cohesive submodules βmodel.rsnow hosts only the call-contract types (agent/tool inputs and outputs, usage, request metadata, function definitions). Chat content (Message,ContentPart, the CBOR-safe wire codecs, data-URL helpers) moved tomodel::content, prompt documents tomodel::document, and text decoding tomodel::text. All names are still re-exported from the crate root, so existing imports are unaffected.- Registry mechanics deduplicated β Group aggregation and name-filtered
definition/function selection are shared by
ToolSetandAgentSetthrough one internalregistrymodule instead of two hand-copied implementations. DocumentsimplementsIntoIteratorβ Consuming iteration over the contained documents;CompletionRequest::append_documentsnow uses it instead of reaching into private fields.- Shared test fixtures β The two duplicated ~300-line mock contexts in
agent.rsandtool.rstests are now oneMockContextin an internal test-support module, and the twin mock-coverage tests merged into one.
- Provider seam unified on
CompletionFeaturesDynβ The delegatinganda_core::CompletionFeaturesimpls on the Anthropic and GeminiCompletionModels are removed (they ignoredresourcesand merely forwarded; OpenAI's models never had them). Code that called the models through that trait should callCompletionFeaturesDyn::completioninstead. - Memory internals no longer public β The
Arc<Collection>fields onmemory::Conversationsandmemory::MemoryManagementare now private, so the storage abstraction can no longer be bypassed; use the methods, or the newConversationRecordsport (below).
- One completion driver for all providers β The four provider adapters
(Anthropic, Gemini, OpenAI Chat, OpenAI Responses) now share a single
drive_completionalgorithm behind an internalWireFormatseam; each adapter contributes only its wire mapping. The raw-history ordering and skip/drain invariants are enforced structurally instead of being hand-copied four times. Only observable difference: the Chat adapter's request debug log message is now"Completion request"like the other adapters. - Raw-history pruning moved behind the provider seam β
CompletionFeaturesDyngainsprune_unanswered_tool_calls/prune_tool_interactionswith conservative default implementations; the completion runner no longer hard-codes any provider's wire shapes. Custom providers can override both with their own typed knowledge. - Workspace sandbox is one implementation β The filesystem tools now
resolve every path through an internal
WorkspaceScope; the shell runtime's separate narrowing logic is gone and shell now honors the sameworkspace/workspacesrequest hints (string, path, or array forms) as the filesystem tools β a strict widening, still bounded by the configured root. The path-resolution helpers other modules could previously call directly are now private to the filesystem module. - Module layout β
context/agent.rs(6.7k lines) is split intocontext/agent.rs,context/runner.rs, and shared test fixtures;extension/mcp.rs(3.6k lines) intomcp/auth.rs(OAuth protocol),mcp/session.rs(transports and lifecycle), andmcp/router.rs(name mapping and call rounds); the background-task registry moves fromhook.rsto a newbackgroundmodule. All previous public paths keep working through re-exports. - Single engine assembly path β
EngineBuilder::build,::empty, and::mock_ctxshare one assembly routine, removing three hand-synced copies.mock_ctxis now documented as the supported way for downstream agent and tool authors to obtain anAgentCtxin their own tests. MockImplemented::model_name()now returns"mock_implemented"instead of"not_implemented", so mocks and the null model are distinguishable (the runner compares model names to detect live model switches).- One tool-call protocol for the built-in extension tools β The nine
extension tools (fetch, the four filesystem tools, todo, note, shell,
skills manager) and the six memory tools now share
extension::tool_definition(parameter schema derived from the typed argument struct,strict: Some(true)) andextension::hooked_call(cancellation gate plusDynToolHookbefore/after wiring), replacing ten hand-writtenjson!schemas and seven copies of the hook boilerplate. The two memory schemas that stay hand-built have hard reasons recorded in comments (KIP definitions come from outside;memory_apiflattens an internally tagged enum thatschemarswould render asanyOf). Observable differences:- Every tool now rejects a call whose context is already cancelled
(previously only
search_fileand the shell background path honored the token); the fetch, skills-manager, and memory tools gain hook support (newFetchToolHook/SkillToolHookaliases; the two KIP tools share theDynToolHook<Request, Response>slot since hooks are keyed by argument/output types β a hook tells them apart through the newBaseCtx::path()). - The note tool's failed operations (missing items, size limit, unknown op)
and the shell tool's executor-failure and timeout outcomes still resolve
to
Okwith the same typed output, but are now flagged withis_error: Some(true)so hooks, providers, and telemetry see the failure signal. A shell command that runs and exits non-zero remains regular output. - Derived schemas carry additional metadata the hand-written ones lacked
(
defaultvalues from serde defaults,minimum: 0on unsigned integers, descriptions on nested item objects); property shapes, enums, and required lists are unchanged.
- Every tool now rejects a call whose context is already cancelled
(previously only
allowed-toolsis now an upper bound β A skill that declaresallowed-toolsis granted exactly those tools as a subagent; only skills that declare nothing inherit the manager's default tool set. Previously the configured defaults were merged into every skill, which could escalate a restriction written in a third-party SKILL.md.
- Rustdoc builds clean again β Intra-doc links in
extension::mcp::authand theextensionmodule docs were left unresolved by the MCP/hook restructuring and failedRUSTDOCFLAGS="-D warnings" cargo doc; they now resolve. - The OpenAI Chat adapter no longer discards the assistant message on a
failure verdict β A non-success
finish_reason(length,content_filter, β¦) previously dropped the whole turn: the truncated text, reasoning, and tool calls vanished and onlyfailed_reasonsurvived. The Chat adapter now matches the Anthropic and Gemini adapters: the message is preserved in bothraw_historyandchat_history, while the extractedcontent/thoughts/tool_callsremain gated on success. - Interrupted tool calls no longer strand an OpenAI Responses
reasoningitem β Dropping unanswered tool-call requests (after steering, discard, or stop) now takes the reasoning item that must immediately precede a pruned call out with it, the way completed-interaction pruning already did. Previously an interrupt mid-tool-call on the Responses adapter left an orphaned reasoning item inraw_history, and every subsequent request was rejected for a reasoning item without its required following item β wedging the conversation until the process restarted. - Live model switch replay β
raw_historybelongs to the model that produced it: replaying one provider's native message JSON (OpenAIinput_textparts, Anthropic content blocks, Gemini parts) through another provider makes the request unparseable and the provider rejects the whole call, wedging the conversation until restart. Each turn now re-resolves the routed model; when it changes, the engine dropsraw_historyand replays the provider-neutralchat_history.
- Explicit remote HTTP opt-in β New global
--allow-httpflag is required before the CLI sends signed requests to a non-loopbackhttp://endpoint. Local loopback endpoints continue to work without configuration.
Changed β anda_core v0.14.5, anda_engine v0.14.5, anda_engine_server v0.14.5, anda_web3_client v0.14.5, anda_cli v0.14.5
- AndaDB 0.10β0.11 β Upgraded
anda_db,anda_db_tfs,anda_cognitive_nexus,anda_db_schema, andanda_kipto 0.11. Conversation pagination now uses the database's newest-first ID query while preserving stable newest-first results across pages.
- rmcp 2.2β3.0 β Updated the MCP client integration for rmcp 3.0, including its peer metadata and OAuth metadata APIs.
- MCP server discovery β Tool-provider groups now retain an MCP server's handshake title, description, and instructions so consumers can present a coherent server capability bundle instead of only a flat tool list.
- On-demand cache namespaces β Agent, tool, runtime-discovered MCP, and subagent cache namespaces are now created lazily while retaining per-namespace isolation and configured capacity limits.
- Workspace-bound filesystem and shell tools β Caller-controlled
workspace/workspacesmetadata can now only narrow to a resolved subdirectory of a configured workspace; it can no longer redirect file or shell operations outside the configured roots. File search additionally caps scanned entries and observes cancellation, andedit_filerejects a replacement result exceeding the file-size limit before allocation. - Conversation resource authorization β
get_resource_contentnow requires the owning conversation and verifies both conversation ownership and resource membership before returning a resource, closing global resource-ID disclosure. Conversation persistence also preserves concurrently queued steering/follow-up messages and clears stale failure reasons after a successful update. - Model-adapter edge cases β OpenAI-compatible completions preserve messages when
finish_reasonis omitted and treat empty tool arguments as{}. Anthropic requests derive matching non-empty tool IDs when an upstream provider omitted one, omit persisted reasoning blocks lacking a valid signature, and bound provider-supplied streaming content-block indexes to prevent oversized allocations. - Callable routing and cancellation β Allowlisted subagents and remote callables now match their advertised routing prefixes; duplicate callable names across local and remote sources are suppressed; cancellation closes visible in-flight tool calls with interruption outputs so persisted histories remain replayable.
- Hook cleanup β When a later agent/tool start hook rejects, all previously started hooks are unwound even if an intermediate end hook fails, preventing stateful hooks from stranding leases.
- Skills and background tasks β Loaded skills retain stable subagent session registries across lookups and reloads; directly reading a skill refreshes its materialized subagent. Background task IDs are namespaced by subagent, preventing same-named sessions from colliding.
- MCP resilience and routing β Tool-list change notifications cannot be lost during a refresh; failed lists retry instead of accepting a stale route table; local-name collisions are safely disambiguated; peer-closed transports reconnect; client-credentials sessions reconnect before expiry; and stdio transport environment values are redacted from
Debugoutput. - Remote engine initialization β
EngineBuilder::mock_ctxnow registers configured remote engines just asbuilddoes, so remote tools and agents are available in mock contexts.
- Credential handling fails closed β Requests carrying malformed authorization headers are rejected instead of silently becoming anonymous, and configured CWT bearer tokens must carry an expiration claim.
- Signed endpoint URL guard β Rejects embedded URL userinfo such as
https://trusted.example@attacker.example/, which could otherwise send a signed request to a misleading host.
- Anthropic structured output β
output_schemawas silently dropped for Anthropic models; now mapped tooutput_config.formatwithjson_schematype, so structured output requests work across all three model families.
- SSE deserialization zero-copy β Wire enums in Anthropic, OpenAI, and Gemini type layers now deserialize by reference (
&str) instead of cloning the bufferedValue, removing per-event deep copies on SSE streaming hot paths. - Reduced per-request cloning β The full conversation is no longer cloned per request; request-log clones now live inside the log branch, raw history is built before converting content blocks, and v2
output/parsed_outputare filled in a single pass. - Shared model helpers β
null_default,resolve_endpoint, andstring_enum_serde!(now with input aliases) hoisted tomodel.rs; applied to five hand-written open string enums. - Consolidated test scaffolding β Triplicated HTTP mock scaffolding merged into
model/test_support.rs. - Deduplicated model infrastructure β Model constructors,
Models::clone/replace, error-chain walkers, and the OpenAI media content-part mapping are now de-duplicated.
Changed β anda_core v0.14.3, anda_engine v0.14.3, anda_engine_server v0.14.3, anda_web3_client v0.14.3
- anda_db 0.9β0.10 β Upgraded
anda_db,anda_db_tfs,anda_cognitive_nexus,anda_db_schema, andanda_kipto 0.10. No API changes required in this workspace.
- MCP OAuth 2.1 authorization β
McpOAuthConfigsupports two flows for Streamable HTTP MCP servers: interactive Authorization Code with PKCE (begin_authorization/complete_authorization/cancel_authorization) and headless Client Credentials (SEP-1046). The library drives the protocol but the consuming application owns the browser, redirect callback, and credential store. - Pluggable credential persistence β
McpCredentialStoretrait withload/save/clearlets applications back OAuth tokens with an encrypted store.InMemoryMcpCredentialStoreis the default for development. McpAuthorizationRequirederror β Typed error returned when a session needs interactive auth; consumers downcast to trigger the authorization flow.discover_http_oauthβ Static method probes an HTTP MCP endpoint for OAuth capabilities (scopes, DCR support) without connecting.register_server/remove_serverβ Register an auth-requiring server without connecting, for deferred connection after the interactive OAuth flow completes.- Validation: no
bearer_token+authmixing βMcpStreamableHttpTransportrejects configs that set both.
- rmcp 1.7β2.2 β Upgraded with the
authfeature, replacing the static bearer-token path withAuthClient-based transport whenMcpOAuthConfigis present. Transport config split intobase_transport_config(for auth client injection) andtransport_config(static bearer, preserved for backward compat). list_rootsremoved β The deprecatedlist_rootsclient handler is removed to match rmcp 2.x.
- CBOR RPC response body cap β
MAX_RPC_RESPONSE_BYTES(16 MiB) enforced with streaming chunk-by-chunk guard; oversized responses are rejected before full buffering, protecting memory-constrained TEE runtimes. RemoteErrorerror variant β Split fromResultErrorso callers can distinguish transport-level decode failures from application-level remote errors.- Documents closing-tag injection guard β
Documents::Displayneutralizes literal</tag>delimiters inside untrusted attachment content (case-insensitive), preventing document content from closing the block early.
- Subagent execution-time tool allowlist β
CompletionRunner::set_allowed_callablesenforces the subagent's tool whitelist at dispatch time, not just in the definitions sent to the model. An empty allowlist rejects every call; discovered tools (from allowed discovery tools) are granted implicitly. - Completion response body cap β
MAX_COMPLETION_RESPONSE_BYTES(64 MiB) guards against a runaway or malicious provider streaming unbounded body. Enforced with streaming chunk-by-chunk guard;Content-Lengthis pre-checked before the first byte. - ModelConfig api_key redacted from Debug β Custom
Debugimpl replaces theapi_keyfield with[REDACTED]so a{:?}log line never leaks a credential. - Export name validation β
EngineBuilder::check_exportsrejects misspelled agent names (hard error) and warns on unresolved tool names before the engine starts. - Root-level cache namespace β
Path::default()is registered on every engine so root-contextcache_get_withcalls (e.g. dynamic remote-engine resolution) hit memory instead of always falling through to the store.
- RemoteTool / RemoteAgent cleanup β Removed the stale
engine: Principalfield; the target engine is resolved fromendpointat call time.RemoteAgentno longer lowercases a caller-provided name (the caller is expected to supply an already-valid lowercase name, consistent withRemoteTool). - Resource selection fixed for remote tools/agents β
select_tool_resources/select_agent_resourcesnow use the same longest-handle + exact-name resolution as endpoint routing, so resources always match the engine/tool the call is routed to even with overlapping handle prefixes. - Engine visibility checks hardened β
ctx_withandctx_with_basenow enforce anonymous/private/protected rules (previously onlyagent_runandtool_calldid).ctx_with_basealso validates the agent name before creating the context. - Anonymous principal excluded from management β
is_controllerandis_managerreject the anonymous principal even when the engine was built without a Web3 identity andcontrollerdefaults to anonymous. - Agent/tool end hooks always paired with start β
agent_runandtool_callnow invokeon_agent_end/on_tool_endon the failure path (with a placeholder output) so hooks that track leases (e.g.SingleThreadHook) release their accounting. - Challenge endorsement guard β
Engine::challengevalidates that the request'sAgentInfomatches the engine's own info (deterministic CBOR comparison) before signing, preventing registry entry hijacking via forged agent-info payloads. - File search always canonicalizes β Every path match is now canonicalized and re-checked against the workspace root, closing the symlink escape: a workspace-internal directory symlink pointing outside can no longer enumerate external filenames.
- ToolsSearch wildcard returns names only β The
*query now enumerates name + description (no parameter schema) and is capped at 64 results, keeping listing cheap and preventing context-window blowout. - Store list strips namespace prefix β
store_liststrips the context namespace fromprefix/offsetso a returnedObjectMeta.locationcan be fed back as paginationoffseton a non-root context without a doubled namespace. - Cache
get_withpreserves error source chain βCacheInitErrorwraps the initializer error instead of flattening it to a string, so downstream code can downcast for retryable/status signals. - Models::from_configs β Disabled models are skipped with
info!; misconfigured models that fail to build are skipped withwarn!instead of being swallowed silently. - OpenAI Chat Completions β
response_format.json_schemanow wraps the schema in the required{name, schema, strict}envelope; streaming requests requeststream_options.include_usagefor billing tracking;CompletionResponsepopulates themodelfield. - Gemini β
tool_choice_requiredis honored viaFunctionCallingMode::Any;tool_use_prompt_token_countmoved fromoutput_tokenstoinput_tokens(it is an input-side count, distinct fromprompt_token_count). - Anthropic β Empty/whitespace-only partial JSON in a tool-use block finalization now preserves the existing
{}default instead of overwriting with an empty string, matching the official SDK'sJSON.parse(buf || \"{}\")guard.
- Context compaction recovery β A transport failure during the summarization turn now restores the runner's tools, discovered tools, queued input, and unbound flag; a retry finds a usable runner instead of a permanently tool-less one.
- Subagent allowlist survives compaction β
handoffnow carriesallowed_callablesinto the replacement runner. Without this the subagent tool whitelist was silently dropped on the first context compaction, letting the subagent call any callable in the engine afterwards. - Unanswered tool calls flushed in discard β
discard_in_flight_requestnow unconditionally closes unanswered tool calls in the visible history, fixing the case where the tool round executed but the follow-up model call failed (pending calls drained, but visible history still held aToolCallwith no result β unreplayable by providers). - MCP cross-server local-name collision β When two MCP servers produce the same local tool name, the newcomer is disambiguated with a stable hash suffix. If the disambiguated name still collides, the tool is dropped with an error log instead of silently hijacking another server's route.
- File search symlink escape β Previously a workspace-internal directory symlink pointing outside let a plain pattern enumerate external filenames. All matches are now canonicalized against the workspace root.
- Memory expiry no longer deletes shared resources β
delete_expired_conversationsnow leaves resources intact because they are content-deduplicated and may be shared by other active conversations; reclaiming orphans requires a dedicated reference-counted GC pass. - Gemini token miscount β
tool_use_prompt_token_countwas incorrectly added tooutput_tokensinstead ofinput_tokens, inflating the output count and undercounting input.
- Serde buffering replaces
serde_json::ValueforContentPartβ Deserialization now uses serde's untagged/type-tagged machinery directly, preserving CBOR byte strings (InlineData.data,Principal,Action.signature) across RPC round-trips that previously lost them through the JSON intermediate. - Path encoding hardened β
path_lowercaseandpath_joinno longer double-encode%in already-encoded object-store keys. Re-joining a namespace with astore_listkey is idempotent. - Definitions/functions deduplicated by lowercase name β
AgentSetandToolSetsuppress duplicate schemas when the same tool/agent is requested multiple times (some model providers reject repeated definition names). /pingwith arguments accepted β/ping nowresolves toPinginstead of being treated as an unknown command; bare/and/ argare plain prompts rather than empty commands.- Blank
failed_reasontreated as success β An all-whitespace failure reason is normalized toNoneinAgentOutput::into_tool_output. - MCP secrets redacted from
Debugβ CustomDebugimpls onMcpStreamableHttpTransportandOAuthClientCredentialsConfigreplace bearer tokens and client secrets with[REDACTED]. validate_function_name: characters β bytes β Name length is now checked in bytes rather than characters.- Dependency cleanup β Removed unused
futuresandserde_bytesfromanda_coreproduction dependencies (futureskept for dev-dependencies).
%double-encoding in object-store paths βFrom<String>re-encodes%to%25, sopath_lowercaseandpath_joinswitched toPath::parse/Path::from_iterto preserve already-encoded segments.cache_store_deleteordering β Store is now deleted before cache to prevent a concurrentcache_store_getfrom repopulating a ghost cache entry that survives the delete.- Inline data token estimation β Switched from
(len + 3) / 4tolen.div_ceil(3)to match base64 expansion ratio (~4/3 chars per byte, not 3/4).
- Signed envelope digest required on RPC β
verify_usernow rejects aSignedEnvelopethat omits its committeddigeston body-bound RPC paths, instead of falling back to the server-computed body hash. This is a fail-closed hygiene check (the client must explicitly commit to the body hash); it is not a standalone defense against a signing oracle sharing the key β the signature is still verified over the same hash, and genuine resistance requires domain separation in the signature scheme. ApiKeyMiddleware::exempt_prefixβ Prefix-based exemption for discovery subtrees with dynamic segments (e.g./.well-known/covers both/informationand/agents/{id}) that cannot be enumerated exactly withexempt_path.
- README rewritten β Accurately describes the server as a thin, stateless forwarder; session management, tool integrations, and access control belong to
anda_engine. - Removed unused
originβ Builder field andwith_originmethod removed (no consumer used it). - Removed unused public
verify_cwtβ Only the internalverify_cwt_tokenis needed. - Decode error hygiene β Param decode failures now use
Display(notDebug) so the client sees the parser's error message without the raw request bytes.
- URL smuggling guard β
check_urlnow parses withreqwest::Urlinstead of a string-prefix check, rejecting non-http(s) schemes (file,ftp,data,ws), bare strings, and URLs with no host. Previously afile:///etc/passwd-style target would pass the prefix guard and attempt a connection. - All-zero root secret warning β
ClientBuilder::buildlogs awarn!when the default all-zero secret is used; the derived identity and all sub-keys are public and predictable. - Identity load improvements β
load_identitydetects existing file paths before falling back to hex decoding, so a PEM file withSecp256k1Identityerrors now surfaces the real parse failure instead of being misinterpreted as a hex string.identity_from_pemno longer masks the Ed25519 parse error when a Secp256k1 parse fails.
- Root secret zeroized β
root_secretfield wrapped inZeroizing<[u8; 48]>; the long-lived copy is wiped from memory onDrop, reducing the exposure window. - Dependencies β Added
zeroizeto workspace dependencies;logandzeroizeare now optional dependencies gated behind theclientfeature. - Query-signature verification (clarified, not changed) β Documented that the default
Agentdoes not request node keys or verify query signatures β the behavior since 0.14.0, now spelled out in a code comment: a non-TEE client reads canister state through a trusted boundary node. Pass your ownAgentviawith_agentto enable verification. - README rewritten β Accurate feature descriptions (ICP canister calls, signed HTTP/CBOR-RPC, deterministic key derivation), feature-flag documentation, and security caveats (endpoints passed to signed calls must be trusted).
CanisterCallerfromBaseContextβ TheCanisterCallerbound is removed fromBaseContext. Runtimes requiring canister access must implement the trait separately on their context type. All built-in impls (AgentCtx,BaseCtx) and theMockCanisterCallertest helper are removed.- Canister methods from
Web3ClientFeaturesβcanister_query_rawandcanister_update_raware removed from the Web3 client trait surface. Runtimes needing raw canister access should use their own client directly. Web3SDKenum andWeb3Clientwrapper βWeb3SDKis now a plain struct wrappingArc<dyn Web3ClientFeatures>instead of aTee/Web3enum. All match-based dispatch is flattened to direct trait-object calls.
- Per-task background cancellation β
BackgroundHandle+BackgroundTaskControlshook primitives with per-task child tokens for shell commands. New/stop_task <task_id>subagent control command stops individual background tasks without disturbing sibling tasks or the session. tee_attestation()onWeb3ClientFeaturesβ New trait method (defaultOk(None)) so TEE-backed clients can attach attestation evidence. Engine'schallenge_responsenow uses a single unified path for both TEE and non-TEE flows.anda_web3_clientfeature flags βclient(non-TEE,ic-agent+ local key derivation),tee(TEE gateway),full(both). Default build pulls neitheric-agentnoric_tee_*crates.crypto+teemodules inanda_web3_clientβ Deterministic key derivation ported fromic_tee_gateway_sdk::crypto(byte-for-byte identical), andTeeClientadaptingic_tee_gateway_sdkto the engine'sWeb3ClientFeaturestrait.
- Dependency upgrades:
ic-agent0.47β0.48,ic_auth_types0.9β0.10,ic_auth_verifier0.9β0.10; new:ic-ed255190.6,ic-secp256k10.3. - Engine dependency diet β Removed
ic_cose,ic_tee_cdk,ic_tee_gateway_sdkfromanda_engine;ic_auth_verifierfeature reduced fromfulltoenvelope. rand_bytesself-implemented β Usesrand::filldirectly instead of re-exportingic_cose::rand_bytes, eliminating the lastic_cosedependency fromanda_engine.
- Base64 blob serialization β Test assertions updated to match
b64:prefix format for inline data and resource blob encoding.
- Background handle typed payload β
BackgroundHandlenow carries a typedArc<dyn Any + Send + Sync>payload viawith_data()/data()methods, replacing the parallelbackground_tasksmap inSubSessionwith data stored directly on the handle.created_atandelapsed_ms()provide lifecycle observability on the handle itself. BackgroundTaskControlsergonomics β InternalMutexupgraded toRwLock; newget()/get_data()/handles()/is_empty()accessors;finish()now returns the removed handle;finish_all()clears all tasks;stop_all()removed with stop logic inlined at call sites.
- Provider raw-history pruning β
prune_req_raw_history()reclaims context-window budget for long-lived subagent sessions by removing consumed tool calls and results from accumulated provider-native JSON history, operating directly on raw JSON to preserve provider-specific shapes (OpenAI Chat/Responses, Anthropic, Gemini) that a Message round-trip would lose. - SSRF protection for model-controlled HTTP fetches β
validate_public_url()blocks requests to loopback, private, link-local, metadata, and unspecified addresses before any outbound connection is made. - Shell process-group cleanup β Spawned shell children now belong to their own process group; cancellation kills the entire group so background descendants are not left behind.
- anda_db 0.8 β 0.9 β Bumped anda_db, anda_db_tfs, anda_cognitive_nexus, anda_db_schema, and anda_kip dependencies from 0.8 to 0.9, adopting the hardened JSON serialization, KQL pagination, and full-scan capping from anda-db 0.9.0.
- Auth failures now return 401 β Bad credentials (wrong signature, tampered body, expired token, wrong target) now return HTTP 401 instead of silently downgrading to anonymous access.
- Store size enforcement β Uploads exceeding
MAX_STORE_OBJECT_SIZEare now rejected at put time with a clear client error.
- Symlink escape via workspace β Filesystem reads now re-verify the canonicalized path resides inside the workspace after resolving symlinks, preventing reads to host files through workspace-local symlinks.
- Subagent conversation persistence β Engine builders can now install a subagent conversation recorder so blocking subagent calls and background sessions are persisted as conversations, expose their conversation IDs, and retain status, usage, artifacts, metadata, and failure information for operational audit.
- Subagent compaction with pending tool calls β Subagent session compaction now executes pending tool calls and records their tool outputs before summarizing, preventing compacted histories from stranding unanswered tool-call requirements.
- Interrupted tool-call history β Completion runners now append explicit error tool outputs when pending tool calls are discarded, stopped, or interrupted by steering, preventing follow-up requests from carrying dangling tool-call state.
- Provider raw-history cleanup β Raw provider histories now prune unanswered tool calls recursively across nested OpenAI Responses, shell, patch, MCP approval, and Gemini function-call shapes while preserving surrounding text and metadata context.
- OpenAI minimal reasoning compatibility β OpenAI model requests now map
ModelEffort::Minimaltolowreasoning effort instead of sending the unsupportedminimalvalue.
- Hyphenated function names β Agent, tool, and function-name validation now allows hyphens (
-) in addition to letters, digits, and underscores, with documentation and tests updated to match the accepted naming rules.
- Mutable completion chat history access β
CompletionRunner::chat_history_mut()exposes ordered mutable access to accumulated chat history messages so callers can update recorded context in place without appending replacement messages.
- MCP peer metadata compatibility β MCP tool discovery now handles the current
rmcppeer-info return shape when capturing server title, description, and instructions for capability groups.
- Child agent context namespaces β Child agent contexts now switch to the child agent namespace while preserving inherited extension state, so nested agent and tool contexts use the correct agent ownership metadata.
- Reusable completion handoffs β Completion runners can now summarize long conversations into compact continuation handoffs and restart from the resulting summary while preserving base tool configuration and accumulated usage metadata.
- Subagent compaction reuse β Subagent session compaction now delegates to the shared completion-runner handoff path, reducing duplicated context-reset logic while preserving session artifacts and tool usage.
- Model request retries β Completion model requests now retry up to three times with a longer retry delay cap, improving resilience to transient provider failures.
- Agent capability groups β Agents can now declare
ToolGroupInfometadata so related agent callables are discoverable as coherent bundles alongside tool and provider groups.
- Agent group discovery β Discovery helpers now include grouped agents in
tools_groups/tools_selectoutputs, preserving bundle instructions and sibling agent member lists when a grouped agent is selected.
- Subagent compaction scheduling β Removed the extra idle-loop compaction trigger so subagent sessions rely on the pending-input compaction path instead of compacting again at every idle boundary.
- Tool capability groups β Tools and dynamic providers can now expose
ToolGroupInfo/ToolGroupmetadata so related callables are discoverable as bundles without changing model-provider function schemas.
- Tool group discovery helpers β Added
tools_groupsplustools_select { group }expansion and group annotations intools_search/tools_selectoutputs, letting agents survey capability bundles first and then load all member schemas on demand. - Built-in filesystem and memory groups β Filesystem workspace tools and persistent-memory/conversation tools now advertise shared capability groups with usage guidance and complete member lists.
- MCP server capability groups β MCP tool providers now capture server title, description, and
instructionsfrom the initialize handshake and surface each server as one discovery-layer tool group. - Subagent live status polling β Added read-only
/statussession polling and manager-levelstatuscatalog output with elapsed time, idle time, token usage, turns, latest progress, and active background tasks. - Grapheme-safe output truncation β Shared truncation helpers now respect Unicode grapheme-cluster boundaries, so shell/status/filesystem inline previews do not split multi-codepoint emoji, flags, skin-tone modifiers, or combining marks.
- Tool group discovery normalization β
tools_groups/tools_selectnow filter stale or shadowed provider group members and merge duplicate group ids before returning discovery output, so group expansion matches the callable schemas actually visible to the current model turn. - Streaming completion request timeout override β Streaming model requests now set their own 10-minute total timeout at the request level, so downstream applications that inject a shared HTTP client with a shorter generic timeout do not abort long-but-progressing SSE completions before the model transport budget.
- Dynamic tool provider contract β Added
ToolProvider,ToolProviderSet, and borrowedBoxFutsupport so runtimes can expose tools discovered at runtime while keeping staticTool/ToolSetbehavior intact.
- Runtime-discovered tool providers β Engine builders can now register dynamic tool providers, merge provider-backed functions into tool discovery, route direct and agent-driven tool calls through providers, and initialize providers during engine build.
- MCP tools extension β Added
anda_engine::extension::mcp, a reusable MCP host/client provider backed byrmcpwith stdio and Streamable HTTP transports, tool allow/deny filters, legal Anda tool-name mapping, dirty refresh ontools/list_changed, audited tool outputs, and explicit exclusion of deprecated Roots/Sampling/Logging control capabilities.
- Streaming completion timeout handling β Model completion clients no longer use HTTP/2 keep-alive PINGs as the liveness detector for long SSE reasoning streams. Completion transport now relies on a per-read body idle timeout plus the existing total request timeout, preventing provider/CDN PING ACK delays from aborting streams that are still producing body chunks.
- Subagent compaction before oversized input batches β Idle subagent sessions now compact before attaching large batched follow-up or steering inputs, preventing background-result bursts from overflowing the context window before summarization can run. Compaction also refreshes session activity so small idle timeouts do not immediately reclaim freshly compacted sessions.
- Slash command argument parsing β Added
PromptCommand::command_argument()so command handlers can consistently extract the user-provided text after a slash command prefix.
- Subagent stop and cancel semantics β
/stop <reason>now stops the current session task while keeping the session idle and reusable, while/cancel <reason>continues to end the session runner. Stopped background task output is suppressed so stale child results are not forwarded after a stop. - Subagent compaction tool discovery retention β Session compaction now preserves whether discovered tool definitions should be merged into future completion requests, avoiding tool-discovery state loss after long session handoffs.
- Runtime model registry replacement β Added
Models::replaceto atomically replace a model registry from anotherModelsinstance, enabling callers to reload model configuration without preserving stale labels from the previous registry.
- Subagent session lifecycle controls β Subagent definitions and the manager tool schema now support an
idle_timeoutsetting for session mode, letting callers tune idle session reclamation while preserving the engine default when unset. - Subagent progress and compaction robustness β Session runners now emit visible progress signals before the next idle boundary, filter signalless tool-call noise, preserve usage/artifacts across context compaction, and fail loudly instead of replacing history with an empty compaction summary.
- Native shell environment injection β Restored the native shell runtime identity so safe host environment variables such as
PATHare forwarded correctly, while keeping background task IDs prefixed with the shell tool name. - Native shell finalization latency β stdout and stderr reader shutdown checks now run concurrently, avoiding doubled grace-period waits when descendant processes keep both pipes open.
- Workspace release and dependency alignment β Bumped the Anda workspace crates to
0.13.0, aligned internal crate dependencies on the0.13series, and upgraded the ICP/TEE/Anda data stack toanda_db_tfs 0.8,anda_db_schema 0.8,anda_cloud_cdk 0.5,ic_cose 0.10,ic_cose_types 0.10,ic-oss-types 1.3,ic_auth_types 0.9,ic_auth_verifier 0.9, andic_tee_* 0.7. - CBOR serialization backend β Replaced direct
ciboriumusage across the published workspace crates withcbor2, using canonical encoding for cache/store data, HTTP RPC payloads, signed Web3 requests, engine server RPC responses, notes, and subagent persistence.
- Transport error diagnostics β Completion transport errors now preserve their source chain and include upstream request IDs, received stream byte counts, and elapsed time for response-body and mid-stream SSE failures, making timeout and upstream-abort diagnosis more actionable.
- Bounded filesystem and shell tool output β File reads now cap inline text and binary previews with explicit truncation markers, glob searches tolerate unreadable or dangling entries while enforcing a scan cap, and native shell execution bounds captured output, terminal-progress rendering, inherited-pipe waits, and cancellation cleanup for background processes.
- Subagent session robustness β Session calls now validate structured arguments, atomically claim session IDs to avoid duplicate runners, report inactive control commands cleanly, preserve usage and artifacts across compaction, forward background usage deltas, and buffer stream steering while a completion step is in flight.
- Conversation and memory edge cases β Conversation batch reads, pagination, search limits, expired-deletion loops, timestamp serialization, resource existence checks, and resource ownership checks are now hardened to avoid default-limit truncation, cursor overlap, dangling references, and cross-conversation resource reads.
- Model transport and response handling β Completion retries now include a short backoff honoring capped
Retry-Afterhints, shared HTTP request timeout allows long reasoning calls, SSE[DONE]detection is line-anchored so generated text cannot truncate streams, and OpenAI Responsesincomplete/failed/cancelledstatuses surface as failure reasons while preserving partial content.
- Embeddable and testable HTTP server router β
ServerBuilder::build_router()exposes the configured Axum router for embedding and integration tests, while engine RPC dispatch now shares CBOR/JSON decoding and result encoding paths, resolves thedefaultengine consistently, and keeps request logging behavior unified. - API key middleware hardening β API key checks now compare equal-length keys in constant time and keep exempt-path configuration clone-friendly.
- Signed HTTP/RPC client hardening β The Web3 client now caches its principal at build time, centralizes HTTPS guard and signed-header construction, avoids echoing secret material in identity-load errors, and shares signed CBOR RPC request construction across async and trait-based call paths.
- Follow-up delivery during tool execution β
CompletionRunnernow queues follow-up messages for the next safe user turn and delivers them after pending tool-call results finish instead of waiting for a fully idle boundary. Tool outputs are preserved in chat history before the follow-up is sent, while steering messages still take priority.
-
Todo tool operation API β Replaced the
todos/mergewrite contract with explicitop=read|set|updateanditemsparameters.setreplaces the list,updatepatches only changed ids, empty ids are ignored instead of materialized, and write calls now return summary counts whilereadreturns the full item list. This reduces tool-call payload size during long-running work and keeps task-list updates focused on changed items. -
Note tool operation API β Replaced substring-based
action=add|replace|removewrites with stable-idop=read|set|upsert|deleteanditemsparameters. Notes now persist in thenotes_v2store, writes return compact summary counts, and read/load operations return structured note items with usage summaries. -
Deprecated extension cleanup β Removed the deprecated
googleandextractorextension modules fromanda_engine, eliminating the legacy Google Custom Search tool and generic extractor helper that were previously marked deprecated. -
Unified child context paths β Agent and tool child context paths now consistently use underscore-separated names such as
a_echo_agentandt_echo_toolon every platform.
-
HTTP client response decoding defaults β Restored
reqwest's default response decoding behavior for the shared model HTTP client instead of globally disabling gzip, Brotli, zstd, and deflate decoding. -
Streaming completion body read resilience β SSE completion readers now return immediately after receiving
data: [DONE], preserving completed OpenAI-compatible streams even if the server or proxy closes the HTTP body with a late transport error.reqwestdecode errors are also classified as retryable model transport failures so upper layers can apply delayed retry behavior instead of treating them as permanent completion failures. -
Structured subagent tool arguments β Subagent calls now preserve full structured argument objects, including
session,model, andeffort, instead of collapsing any object with apromptfield down to the prompt string. This keeps asynchronous/session subagent calls working while preserving legacy plain-string and single-promptagent behavior.
- Adaptive discovered-tool request merging β
CompletionRunnernow tracks schemas returned bytools_searchandtools_select, promotes repeatedly selected discovered tools into subsequent request tool definitions, and compacts repeated discovery outputs once schema merging is enabled. This lets long-running agents call tools after repeated discovery without resending full schemas in every tool-output context. - Identity-encoded completion transport β The shared model HTTP client now disables automatic response decompression so streaming readers can consume raw SSE bytes even when a provider or proxy mislabels
Content-Encoding.
- Failed in-flight tool-result cleanup β Added
CompletionRunner::discard_in_flight_request()to clear stale request content, pending tool calls, and dangling raw tool-call history after a transport-level model failure before processing newly queued input.
- Legacy text decoding support β Added shared text encoding helpers that keep UTF-8 as the preferred path while allowing platform-local legacy text fallback on Windows. Resource-to-text conversion now uses MIME-aware fallback decoding for text-like resource blobs without treating binary media as text.
- Filesystem, shell, and skill tools handle platform-local text encodings β File reads, writes, shell output previews/progress, and
SKILL.mdloading now decode or encode supported text encodings such as GBK when needed, preserving UTF-8 behavior by default and keeping binary/unsupported data on the base64 or error paths. Shell progress streaming now preserves multibyte character boundaries for both UTF-8 and common legacy multibyte encodings. - Streaming completion responses are more robust β OpenAI-compatible, Anthropic, and Gemini streaming requests now ask providers for identity-encoded event streams and the shared parser accepts BOM-prefixed SSE, NDJSON, plain JSON event payloads, and JSON arrays. This avoids provider/proxy response-shape surprises while preserving existing SSE handling.
- Windows-compatible agent and tool context paths β Agent/tool child context paths now avoid colon separators on Windows while preserving existing colon-based namespaces on Unix-like systems, keeping existing deployments stable and making Windows storage paths valid.
- Native shell final progress flush β Background shell tasks now emit any final complete stdout/stderr progress lines before the background-end hook, so short-lived commands do not lose their last progress update when they exit before the next progress interval.
- Portable skill output paths β
skills_managernow normalizes relativeSKILL.mdpaths with/separators on Windows, matching the existing API output shape on Unix-like systems.
- BOM-only resource text is filtered out β Legacy text decoding now rejects non-empty byte slices that decode to an empty string, preventing Windows fallback decoding from turning BOM-only binary or empty-looking resources into empty prompt documents.
- Model completion retry and retry metadata β Added a shared
ModelErrortype and provider request helper that retry transient completion failures once across OpenAI-compatible, Anthropic, and Gemini adapters. Retryable HTTP statuses now include request timeout, rate limiting, 5xx gateway/server failures, and provider-specific529; exhausted retryable failures expose retryability, HTTP status, andRetry-Aftermetadata for upper-layer delayed retry decisions. Streaming response read failures now use the same retryable transport error path, and Anthropic streamoverloaded_error/rate_limit_errorevents are marked retryable. - Tool discovery wording clarified β Updated
tools_searchandtools_selectdescriptions to make schema discovery explicit: returned callable schemas live in tool-output context and should be called directly instead of being dynamically inserted into subsequentCompletionRequest::tools. Added regression coverage that selected tool schemas remain available as context without expanding the request tool list.
- Recursive strict JSON Schema normalization β
normalize_strict_schema()now recursively appliesadditionalProperties: false,properties: {}, andrequired: []defaults to all nested object schemas, not just the root. Previously propertyless nested objects (e.g.parameterswithout explicitproperties) were left open, breaking strict-mode contracts for tools with nested object parameters. Added tests for nested propertyless object closure and recursive normalization.
- Reasoning content merged into message text β
ContentPart::Reasoning { text }inmessage_into()is now appended asContentItem::Textin the current message content block instead of being emitted as a separateMessageItem::Reasoning. This simplifies the OpenRouter-facing message structure and avoids injecting standalone reasoning items that providers may reject or misinterpret.
- Multiple skill directories in
SkillManagerβ AddedSkillManager::new_with_dirs()so skill loading and lookup can scan the default skill creation directory plus additional read-only skill roots. Skill descriptions now list all configured directories, duplicate directory entries are deduplicated, duplicate skill names are skipped after the first load root, and displayedSKILL.mdpaths are made relative to the matching configured root. Added coverage for loading and reading skills across multiple directories.
-
Subagent session resource and compaction edge cases β Resource-only session follow-ups now run instead of being dropped, resource attachments are converted independently so one invalid resource no longer discards all content, compaction handoff calls temporarily disable tool definitions while preserving them for subsequent turns, final session output falls back to the latest visible progress when compaction produces no reportable result, and background session start hooks are emitted only after acknowledgement hooks succeed. Added regression coverage for resource-only follow-ups, compaction finalization, tool restoration after compaction, and failed acknowledgement hooks.
-
Agent completion runner edge cases β Fixed completion streams so a pending model future is retained across polls instead of being recreated, document-only requests now execute without requiring prompt/content text, steering after tool calls prunes only unanswered raw tool-call items while preserving prior raw history and assistant reasoning, completed runners ignore late steering/follow-up queues, and remote agent resource selection now uses the unprefixed agent name. Added regression coverage for the stream polling, raw-history pruning, steering, and document-only paths.
- Hardened OpenAI-compatible response parsing β Chat Completions and Responses now tolerate DeepSeek-compatible variants: nullable usage detail objects and counters, unknown
service_tierstrings,reasoningaliases for reasoning content,tool_call/tool_use/function_callfinish reasons, JSON object tool arguments/inputs, and missing message/tool-call role/type/id fields. Added tests for non-streaming and streaming compatibility shapes. - Expanded provider compatibility tolerance β Anthropic streaming now ignores unknown stream events and content deltas instead of failing; Gemini now preserves unknown enum values, accepts nullable usage/safety metadata, and treats missing/unspecified finish reasons as successful candidate output. OpenAI Responses now preserves unknown statuses and streaming tool-call chunks can omit indexes. Added provider tests for these compatibility shapes.
- Removed fallback model support β Removed the
fallback_modelregistry slot fromModels, thewith_fallback_model()builder API, thefallback_model()accessor, fallback routing fromget_model()andresolve(), and all fallback-related completion logic inCompletionRunner::step(). Afallbacklabel is now an ordinary label with no special runtime behavior. Model routing uses the primary model plus explicit labels only. - Refactored response body reading β Replaced
response.text().await+serde_json::from_strwithresponse.bytes().await+serde_json::from_sliceacross OpenAI, Anthropic, and Gemini completions. Error messages now useString::from_utf8_lossyfor safer non-UTF-8 body display. - Improved subagent output lifecycle β Added
with_session(),latest_output(),finalize_output(), andrecord_failed_output()helpers toSubSessionRunnerfor consistent session tracking and error reporting. Cancellation failures and compaction errors now properly record their output before returning errors. Fixedneeds_compaction()panic oncontext_window == 0by treating it as unlimited. Added 3 new tests for output lifecycle and edge cases.
utf8_text_from_bytes()andutf8_text_from()β New text-detection helpers that check for excessive control characters (β€5%) in the first 4KB before treating a byte buffer as text. Prevents binary blobs from being incorrectly rendered as garbled text inContentPartandDocumentconversions.- Refined blob-to-text conversion β
ContentPart::TryFrom<Resource>andDocument::From<&Resource>now use the new text-detection functions instead of naiveString::from_utf8.Documentconversion also usesResourceRefto excludeblobfrom metadata and now correctly includes blob text content when detected as text.
ContentPart::Anyhandling unified across providers β Refactored theContentPart::Anyβ typed-part conversion paths in OpenAI, Anthropic, and Gemini modules to consistently (1) attempt deserialization into the provider's known typed form, and (2) fall back to wrapping the original JSON as a text part. Each provider now uses a dedicated helper (chat_completion_content_part_from_any/content_item_from_any/message_item_from_any/content_block_from_any/part_from_any) and explicitly filters out the catch-allAnyvariant so a round-trip throughAnynever silently replaces a typed part. This guarantees that structured content (e.g.input_image,web_search_call,tool_use) round-trips with full semantics, while truly unknown JSON is preserved verbatim as a stringified text payload for downstream inspection.
to_message_inputs_only_preserves_known_any_content_partsβ New test inanda_engine/src/model/openai.rsasserts thatContentPart::AnyJSON with a knownimage_urlshape is preserved as-is, while unknown shapes are wrapped as text with the original JSON serialized inside.message_into_only_preserves_known_any_itemsβ New test inanda_engine/src/model/openai/types.rscovers bothContentItemandMessageItemdeserialization paths: a knowninput_imagestays in the user message content, a knownweb_search_callbecomes its ownMessageItem, and an unknown shape falls back to a text payload carrying the original JSON.- Anthropic
ContentBlock::Anytest coverage β Extended the existingcontent_part_into_preserves_anthropic_specific_variantstest inanda_engine/src/model/anthropic/types.rswith three additional cases: rawtextblocks round-trip asContentBlock::Text, unknowntypevalues fall back to text-wrapped JSON, and malformedtool_useshapes also fall back to text-wrapped JSON instead of erroring. content_part_any_only_preserves_known_gemini_partsβ New test inanda_engine/src/model/gemini/types.rscovers the same fallback contract for Gemini parts: rawtextshapes becomePartKind::Text, unknown shapes are wrapped as text, and malformedfunctionCallshapes fall back to text-wrapped JSON.
Models::resolve()simplified fallback chain β Removed the deprecatedfallback_model()path and the arbitrary-first-model extraction fromresolve(). The resolution order is now: exact label match βget_model()(which returns the primary model, or the first available if none is configured). This eliminates a code path that could silently return an unexpected model and alignsresolve()with the well-testedget_model()behavior. Updated doc comment and test expectation accordingly.
-
ContentPart::any_fromandany_intohelpers β New typed constructors forContentPart::Any:any_from::<T>(ty, val)builds aContentPart::AnyJSON value with a"type"field, andany_into::<T>(ty)deserializes it back, verifying the type tag and returningErr(self)on mismatch. Enables type-safe round-trips for structured JSON content (e.g.Resource) without routing through specific provider types. -
test_content_part_any_from_and_any_into_resourceβ New test inanda_core/src/model.rsvalidates round-trip serialization ofResourcethroughContentPart::Any, covering all fields includingblob(base64),metadata,uri, andmime_type. Also verifies type mismatch returnsErrand non-Resourceinput is rejected.
- Model-aware subagent manager descriptions β
SubAgentManagernow carries amodels: Vec<String>field set viawith_models()builder. When models are registered, the tool description dynamically includes available model names for routing decisions (e.g. "This manager supports the following models for routing decisions: flash, pro, primary."). resourcesfield in conversation storage βConversation::to_fields()now persists theresourcesfield alongsidemessagesandartifacts. Previously, resources attached to a conversation were lost on save/load cycles.ContentPart::AnyResource serde test β Newtest_content_part_any_supports_resource_serdevalidates round-trip serialization ofResourcethroughContentPart::Any, covering all fields includingblob,metadata,uri, andmime_type.
- Effort enum values:
xhighβmaxβ SubAgent and SubAgentManager function schemas now list"max"instead of"xhigh"in theeffortenum, aligning with theModelEffortrename in v0.12.24. Updated in three locations (subagent schema, manager schema, test assertion). model_names()doc comment β Added documentation for theModels::model_names()method describing its return value.ModelConfig.effortdoc:xhighβmaxβ Documentation string updated to reflect the renamed variant.
ModelEffortβ provider-agnostic reasoning/thinking effort β NewModelEffortenum (minimal,low,medium,high,max) provides a single vocabulary for reasoning effort across all model providers. Includesas_str()andDisplayimpl.- Per-request effort on
CompletionRequestβCompletionRequestnow carries aneffort: Option<ModelEffort>field, enabling callers (including subagents) to select reasoning effort per completion rather than only at model-config time.
- Tool error propagation via
is_errorβToolOutputandContentPart::ToolOutputnow carryis_error: Option<bool>. Agent runner setsis_error: Some(true)on tool/agent execution failures and tool-not-found paths. Memory tools signal Nexus errors through the same flag. Anthropic passesis_errorthrough toToolResult; Gemini routes errors toFunctionResponseValue.errorinstead of the normaloutputfield. - Subagent catalog, operation routing, and runtime state preservation β
SubAgentManagergainsoperationfield ("upsert"or"list"),catalog()method returning all registered subagents with metadata, andpreserve_runtime_state()to keep active subsessions alive across upserts.SubAgent::definition_description()now enriches descriptions with tags, allowed tools, output schema info, active sessions, default model, and default effort. Session-mode responses include session ID. - Provider-agnostic
ModelEffortwith per-request application βModelEffort(promoted from engine to core) maps to each provider's native reasoning level: AnthropicOutputEffort(saturates atMax), GeminiThinkingLevel(saturates atHigh), OpenAIReasoningEffort(now includesMaxvariant), and OpenAI Responses v2Reasoning. All three providers now applyreq.effortduringcompletion(), not only model-config defaults. - Subagent model and effort selection β
SubAgentandSubAgentArgsgainmodel: Stringandeffort: Option<ModelEffort>fields, with case-insensitive deserialization (accepts"HIGH","max",null, empty string, or JSON value). Session compaction preserves model and effort across context cycles. BothSubAgentandSubAgentManagerfunction schemas exposemodelandeffortparameters. - Model label resolution β
AgentCtxnow resolves models by label viamodels.resolve(label)instead of the previous name-only lookup.CompletionRunnergainsset_model()andset_effort()methods for mid-run model/effort switching. - Subagent tests for model/effort selection β New
subagent_run_allows_model_and_effort_selectiontest verifies end-to-end flow: subagent receives a model label, the correctCompletionRequestreaches the provider with the expected model and effort. NewRecordingRequestCompletermock capturesCompletionRequestfor assertion. All existing tests updated for new fields.
- Anthropic error messages include model name β Non-200 HTTP responses now include the model name in the error text for easier debugging of provider failures.
- Shell foreground commands auto-move to background after 42 seconds β Previously native foreground commands were waited on with
wait_with_output(), and if they exceededSHELL_TIMEOUT_SECS(180s) the tool would return a timeout error. Nowexecute_commandusestokio::select!withSHELL_AUTO_BACKGROUND_SECS(42s): if the process hasn't finished within that window, it is transparently moved to background execution with a "moved to background" message containing the task ID for hook delivery. TheRunningProcessstruct now bundles child/stdout/stderr/readers immediately after spawn, and extractedfinalize_process_output()is shared between the foreground-completed and background-completed paths. Added testexecute_auto_moves_long_running_foreground_to_backgroundverifying the auto-transition and hook delivery. - Comprehensive sub-session compaction tests β New mock infrastructure (
UsageCompleter,RecordingCompactionCompleter,RecordingAgentHook,request_texthelper) and three new tests:needs_compaction_respects_usage_thresholdverifies compaction triggers at exactly 100,000 input tokens;needs_compaction_triggers_at_turn_limitverifies compaction afterMAX_TURNS_TO_COMPACTturns with unbound runner;subsession_runner_compacts_context_and_continues_from_handoffexercises the full compaction flow β context is compacted into a single assistant handoff message, instructions/role/output_schema are preserved, and subsequent follow-up input resumes correctly from the compacted history.
- SubAgentManager now isolates subagent storage under
subagents/prefix β Previouslystore_listwas called with no prefix filter (loading everything from root), and subagents were stored directly at the root level. Now bothload()andsave()usestore_prefix() = "subagents"as the listing and storage path prefix, separating subagent data from other store entries. Added test verifying that a legacy agent stored at root level is not loaded bySubAgentManager::load. - Fixed
ContentPart::FileDataconversion for non-remote URIs β In Anthropic, Gemini, and OpenAI (bothto_message_inputsandmessage_into),ContentPart::FileDatanow guards onfile_uristarting withdata:orhttps://before converting to provider-native blocks (Image, Document, File, Video, Audio). Non-remote URIs likefile://now correctly fall back to JSON-serialized text content blocks instead of being sent as inaccessible document/file references to the API. Added tests across all four conversion paths.
- Removed
modelfield fromAgentCtxβ The cached default model instance is no longer stored on the context. Insteadmodels.get_model()is called inline at the point of use (completion_runner,model_name()). Child and spawn agent contexts no longer need to clone the parent's model. This reduces cloning and simplifies the context lifecycle. - Compaction threshold raised to 80% of context window β
needs_compactionnow triggers at 80% (saturating_mul(8) / 10) instead of 50% (saturating_div(2)), with the minimum raised from 50,000 to 100,000 tokens. Fewer unnecessary compactions, preserving more context across turns.
normalize_schema_objectnow correctly handles["object", "null"]types β Theis_objectcheck previously only matched"type": "object", missing nullable object schemas like["object", "null"]. Newschema_type_contains_object()helper checks both string and array type values. Also,additionalProperties: falseis now set for all object schemas, not only those withpropertiesβ so{"type": ["object", "null"]}without properties also gets it. Added testtest_normalize_strict_schema_handles_nullable_objectscovering both cases.- OpenAI Responses v2: filter empty Reasoning and ItemReference from history β
normalize_message_itemnow returnsNoneforReasoningitems withencrypted_content: None(empty reasoning blocks) andItemReferenceitems (by-ID references with no content). Previously these passed through as-is and could cause provider errors.raw_history_intonow chains.filter_map(normalize_message_item)on the coreMessagehistory path too, which previously bypassed normalization.
output_schemain subagents_manager now uses["string", "null"]β The JSON schema is submitted as a JSON-encoded string instead of a nested object. This avoids the structured-output problems that nested object schemas cause with strict function-calling providers. Addeddeserialize_optional_json_schemacustom deserializer that accepts JSON strings (parsed inline) ornull. New tests:subagents_manager_definition_uses_strict_safe_output_schema,subagents_manager_args_accept_json_encoded_output_schema.
- Stripped non-essential JSON Schema keywords from tool definitions β Removed
default,uniqueItems,minLength,minLength(input/description),pattern,maxLength,minimum, andmaximumfrom all tool schemas. These keywords add unnecessary strictness in a world whererequiredalready lists every field; some providers may also fail to handle them correctly in strict function calling mode. Affected tools:tools_search/tools_select: removeddefaultanduniqueItemsskills_manager: removedpattern,maxLengthlist_conversations/search_conversations: removeddefault,minimum,maximumsubagents_manager/subagent: removedminLength,uniqueItems,pattern
- Removed automatic
max_output_tokensinjection βCompletionRunnerno longer appliesself.model.max_outputas a defaultmax_output_tokenswhen the request omits it. This responsibility now belongs to each provider's completion method or the caller, giving more precise control over token limits per request. Previously the runner automatically setreq.max_output_tokens = Some(self.model.max_output)for any request without an explicit limit.
- Strict schema normalization β
normalize_strict_schema()inanda_core::jsonrecursively normalizes JSON schemas for strict function calling: rewritesrequiredto contain every key inproperties, defaultsadditionalPropertiestofalse, and traverses nested schemas (items,$defs,allOf/anyOf/oneOf,if/then/else,not, etc.). NewFunctionDefinition::normalize_strict_parameters()method calls this normalization whenstrict: true. All tool definitions (ToolDefinition::from,CompletionRequesttool-building) now go through this normalization path β ensuring consistent strict schemas regardless of provider. - Model-level stream control β Added
stream: boolfield toModelConfigandwith_stream()builder methods on all completion model types: Anthropic (CreateMessageParams.stream), Gemini (GenerateContentRequest.stream), OpenAI Chat Completions (ChatCompletionRequest.stream), and OpenAI Responses v2 (CompletionRequest.stream). Each provider'sCompletionModel::new()now readsself.streamand sets the default request accordingly. - OpenAI Responses v2 defaults to streaming β The v2 model now defaults to
stream: truewithstore: falsefor stateless requests. The originalreasoningdefault parameter was removed. - OpenAI Responses v2 message history normalization β New
raw_history_into()converts rawJsonhistory intoMessageItemvalues with proper role-aware normalization: coreMessagehistory is unwrapped viamessage_into(), legacy ResponsesMessageItem::Messagerecords are normalized throughnormalize_message_item(), and unrecognized values pass through asAny. Assistant messages in history now useoutput_textcontent type (required by Responses API) rather than plaintext. Content-type filtering distinguishes input (text) from output (output_text) based on role. - OpenAI Responses v2 stream aggregation β
responses_response_from_stream_events()now handlesResponseOutputItemDoneevents: when the final response has an emptyoutputarray, output items collected fromdoneevents fill in viaoutput_indexordering.
- All tool schemas now declare complete
requiredarrays β Every tool definition schema lists all property keys inrequiredwithadditionalProperties: false, including optional fields. Previously many tools omitted optional fields fromrequired, which triggers strict schema validation failures with providers. Affected tools:tools_search,tools_select,list_conversations,search_conversations,memory,note,todo,subagents_manager,read_file,write_file,edit_file,search_file,shell,extractor(SubmitTool). anyOfavoidance in tool schemas βnotetool now uses"type": ["string", "null"]with inlineenumcontainingnullinstead ofanyOf-based union schemas.todoandsubagents_managertools similarly use["array", "null"]and["object", "null"]type arrays. These patterns avoidanyOfwhich many providers (especially Anthropic) reject in strict mode.- Memory tool gets hand-written schema β Replaced auto-generated schema with a manually crafted one that lists all 9 operation types in a flat
typeenum with all optional fields explicit, avoiding the nested discriminator pattern that produced invalid schemas across providers. - Gemini default request β Removed the hard-coded
top_p: 0.95default fromCompletionModel::new(). - Subagent
sessiondescription β Changed from "Omit session" to "Leave session empty" to match strict schema nullability. - All tests updated β Test function definitions, schema assertions, and expected values updated to include full
requiredarrays andadditionalProperties: false.
- SSE streaming support across all backends β Anthropic, Gemini, OpenAI Chat Completions, and OpenAI Responses backends now support true SSE (Server-Sent Events) streaming. Previously responses were always read as complete JSON payloads; now streaming responses are parsed and aggregated from
text/event-streamchunks. Added genericread_sse_json_events<T>()inmodel.rswith proper line-buffering, UTF-8 validation, and multi-linedata:event concatenation.- Anthropic:
response_from_stream_events()reconstructsCreateMessageResponsefrom stream events β handles MessageStart, ContentBlock{Start,Delta,Stop}, MessageDelta, and MessageStop. Content blocks (text, thinking, tool_use, server_tool_use) are incrementally assembled; cursor deltas accumulate non-zero fields. Streaming enabled when requeststream=true. - Gemini:
response_from_stream_chunks()aggregates:streamGenerateContent?alt=sseSSE responses. Candidates with the same index are merged β text parts are concatenated, finish_reason/safety_ratings/citation_metadata from later chunks overwrite earlier, and non-empty fields from later chunks supersede earlier defaults. - OpenAI Chat Completions:
chat_completion_response_from_stream_chunks()reconstructsCompletionResponsefromchat.completion.chunkSSE events. Stream delta accumulation handles: text content concatenation, content parts extension, tool call incremental assembly (id/type/function/custom), reasoning_content, refusal, function_call, and finish_reason. Tool calls are built viaToolCallStreamBuilderwith per-indexBTreeMaptracking. - OpenAI Responses:
responses_response_from_stream_events()extracts the most recent response from events β picks upresponse.created/in_progress/completed/failed/incomplete. Parses output after reconstruction.
- Anthropic:
- All streaming backends set
Accept: text/event-streamheader and conditionally dispatch to streaming vs non-streaming code paths based on request configuration. - Gemini:
streamrequest flag β Addedstream: boolfield toGenerateContentRequest(serde-skipped, local-only) for choosing the streaming endpoint. - Unit tests:
aggregates_anthropic_stream_events(text + tool_use with partial JSON),aggregates_gemini_stream_chunks(text concatenation across chunks),aggregates_chat_completion_stream_chunks(text + streaming tool calls),aggregates_responses_stream_completed_event(response.completed extraction).
CompletionRunner::is_idle()β New method that reports whether the completion runner has no pending work: prompt, content, documents, steering message, follow-up message, and pending tool calls are all empty. Useful for polling-based control loops that need to detect when the agent is ready for new input.
- Subagent
tagsdescription refined β Added concrete examples (image,text,audio) to thetagsfield description in thesubagents_managertool schema.
- Error messages include model name across all backends β HTTP request send failures and response body read failures in Anthropic, Gemini, OpenAI, and OpenAI V2 backends now include the model name in the error message. Previously these relied on bare
?propagation, yielding opaque errors like "connection refused" with no model context, making multi-model debugging difficult. - OpenAI: simplified model reference β Removed redundant local
let model = self.model.clone(). Logging and errors now user.modelconsistently.
- OpenAI: Multi-tool-output splitting β
to_message_inputrefactored toto_message_inputs, now returningVec<MessageInput>. When a singleMessagecontains multipleToolOutputcontent parts, each output is emitted as a separate tool-role message with its owntool_call_id. This fixes the case where multiple tool outputs were flattened into one message, which violated the OpenAI API contract (one tool message per call). - OpenAI: Non-text content part preservation β
MessageOutput β Messageconversion rewritten. The oldtext()filter that discarded non-text parts is replaced withchat_completion_content_into_parts/chat_completion_content_part_into/file_data_content_partpipeline. Image URLs, input audio, files, video URLs, and refusals now survive the round-trip conversion as properContentPartvariants instead of being silently dropped. MessageInputnow carriesnameβname: msg.name.clone()is propagated toMessageInputduring conversion.
implicit_contextinjection timing β When pending tool calls are executed mid-turn, the implicit context is no longer injected on the same request. Instead it's deferred to the next user-facing turn, preventing implicit context from being consumed on tool-result rounds where it would have no effect.- OpenAI: DeepSeek
tool_choicecompatibility β Skip settingtool_choicefor models whose name starts with"deepseek", as DeepSeek's API does not support this parameter and returns errors when it is present.
implicit_contextonCompletionRunnerβ newimplicit_context(&mut self, message: Message)method stores a context message that is automatically injected into the next request'schat_historyand consumed on use. This enables steering/follow-up messages to pass contextual information without manual request manipulation.- Prompt ordering fix across all providers β system prompt now uses
content.insert(0, req.prompt.into())instead ofcontent.push(), ensuring the prompt appears before the conversation content in all three model backends (Anthropic, Gemini, OpenAI, and OpenAI v2). This fixes cases where the prompt was appended after content instead of leading. - OpenAI:
modelfield fix βCompletionModelnow explicitly setsr.model = self.model.clone()on each request, fixing a bug where the model field in the cloneddefault_requestwas not being updated to the current model selection.
Resource β ContentPartconversion now usesTryFromwith MIME detection βimpl TryFrom<Resource> for ContentPartreplaces the infallibleFromimpl. Binary blobs now useinfer2to detect the actual MIME type from bytes instead of defaulting toapplication/octet-stream. Resources with neither blob nor URI returnErr(res)instead of serializing to text.inline_data_from_data_urlβ New helper to parse data URLs (data:[<mime>][;base64],<data>) and plain base64 strings into(ByteBufB64, mime_type)pairs. Handles both base64-encoded and percent-encoded payloads.decode_percent_encoded_bytesβ Internal helper for percent-decoding URL-encoded byte sequences.- Comprehensive test suite for
anda_core::modelβ Added 10 test functions coveringAgentInput,ToolInput,PromptCommand,AgentOutput::into_tool_output, data URL round-trips,ContentPart::try_from(Resource)edge cases,RequestMeta,Usage::accumulateoverflow,FunctionDefinition,Document/Documents, andMessagedeserialization.
- Anthropic: Extended API surface β Full support for the latest Anthropic Messages API:
SystemPromptenum (string or content blocks),CacheControlEphemeralfor prompt caching,OutputConfigwithOutputEffortandJsonOutputFormat, structuredStopDetails::Refusal,ToolChoiceconstructors (auto(),any(),tool()),ThinkingDisplayandThinkingType::Adaptive/Disabled, extendedContentBlockvariants (document, search_result, server_tool_use, web_search/fetch results, code execution results, container_upload),ToolResultContentas text-or-blocks,CitationsConfig/TextCitation,UsageServiceTier/CacheCreation/ServerToolUsageonUsage,Containerin responses. - Gemini: Extended API surface β
SafetySetting/HarmBlockThreshold,cached_content/service_tier/storeonGenerateContentRequest,ModelStatus,GroundingAttribution/GroundingMetadatawith rich chunk types (web, images, maps, retrieved context),LogprobsResult,UrlContextMetadata,SpeechConfig/VoiceConfig,ImageConfig/MediaResolution,Modalityenum,response_json_schemafields,seed,enable_enhanced_civic_answers, extendedFinishReasonvariants.SatisfyRatingrenamed toSafetyRatingwith backward-compatible alias. Fixedsatefy_ratingstypo with serde alias. - OpenAI: Full
ChatCompletionRequesttype β Structured request builder replacing ad-hoc JSON construction. Supportsaudio,modalities,reasoning_effort,response_format(text/json_object/json_schema),service_tier,stop(string or array),stream_options,tool_choice(none/auto/required/allowed_tools/function/custom),verbosity,web_search_options,prediction,prompt_cache_key/prompt_cache_retention,logprobs/top_logprobs,safety_identifier,seed,store,metadata,user,parallel_tool_calls,frequency_penalty/presence_penalty. - OpenAI: Content types and refusal handling β
ChatCompletionMessageContentsupports text, content parts (text/image_url/input_audio/file/refusal), andnulldeserialization. Refusal detection from both legacyrefusalfield and content-block refusals.MessageOutputnow usesChatCompletionMessageContentand provideshas_output()/has_refusal()helpers. - OpenAI: Custom tool support β
ToolDefinitionas enum withFunctionandCustomvariants.CustomToolDefinitionwith text/grammar format.ToolCallOutputsupports bothfunctionandcustomcall types.CustomToolCallwith rawinputstring. - OpenAI: Tool calls extracted to
tool_callsfield β Assistant messages now serialize tool calls in a top-leveltool_callsarray alongsidecontent, matching the OpenAI API shape. - OpenAI: Usage details β
CompletionTokensDetails(reasoning_tokens, audio_tokens, accepted/rejected_prediction_tokens) andPromptTokensDetails(audio_tokens). - OpenAI: Media type routing β File/image/audio/video
ContentPartitems now route to the correct content block type (image_url,input_audio,video_url,file) based on MIME type. - OpenAI Responses API v2: Extended types β
StreamEventenum with 11 event types.MessageItemexpanded with file_search_call, computer_call, web_search_call, tool_search, compaction, image_generation, code_interpreter, shell calls, apply_patch, MCP calls, custom tools.ToolDefinitionexpanded with file_search, computer, web_search, MCP, code_interpreter, image_generation, local_shell, shell, custom, namespace, tool_search, apply_patch.ContextManagementandResponseConversationfor conversation state. - Model routing β OpenAI models starting with
gptnow usecompletion_model_v2(Chat Completions API), while non-gpt models use the standard Responses API path. - SubAgent:
FromβTryFrommigration β SubAgent resource-to-ContentPart conversion updated to use the newTryFromimpl.
- Case-insensitive model label lookup β
Models::get(),Models::contains(), andModels::resolve()now normalize labels withto_ascii_lowercase()before lookup. Labels are stored lowercase ininner_set. This meansget("GPT-4")andget("gpt-4")resolve to the same model. - Model names auto-registered as labels β
inner_set()now appendsmodel_name.to_ascii_lowercase()to the label set. A model withmodel_name = "primary"is now findable viaget("primary"), removing the need for manual label aliasing.
CompletionRunnertools_select auto-loading β the completion loop no longer parsestools_selectresults to automatically inject selected tool definitions into the next turn. Tool selection/loading is now handled externally by the calling context, simplifying the runner's responsibility and removing ~25 lines of specialized handling code.is_tools_select_name()helper removed β no longer needed after the above simplification.- ToolsSelect/ToolsSearch integration tests β
ToolsSelectFlowCompleter,ToolsSelectQueryFlowCompleter, andToolSelectorCompleter(~280 lines of test infrastructure) removed alongside the auto-loading behavior they tested.
- Relaxed ToolsSelect/ToolsSearch parameter constraints β
minLength,minItems, andminimumconstraints removed from JSON schemas. These validations now happen at the implementation level, giving models more flexibility in parameter usage. - Optimized
select_requested_names_with_modelserialization β newToolItemRefstruct serializes onlyname+descriptionwhen passing candidates to the selector model, instead of fullFunctionDefinition(which includes large parameter schemas). Reduces token usage.
local_date_hour(now_ms: u64) -> Option<String>β converts a Unix millisecond timestamp to a local datetime string in"YYYY-MM-DD HH(AM/PM) Β±TZ"format.- Test coverage for
lib.rsutilities βrand_number,rfc3339_datetime,json_set_unix_ms_timestamp,json_convert_rfc3339_timestamp, andlocal_date_hournow have comprehensive tests.
with_caller()for context cloning βBaseCtx::with_caller(caller)andAgentCtx::with_caller(caller)clone the context with a new caller principal while preserving all extensions and internal state. Useful for sub-operations that execute under a different identity.
- Background progress interval 3s β 5s β reduces noise for long-running commands (e.g., model inference, large builds). New
NativeRuntime::background_progress_interval()builder method allows per-runtime customization for environments that need faster or slower tick rates.
- 2D terminal emulation for shell progress β
TerminalProgressStateupgraded from single-line buffer to full 2D terminal model with multi-line scrolling, cursor row tracking, and dirty-row incremental output. Supports CSI cursor movement (A/B/C/D), absolute positioning (G/H/f), line/screen erase (J/K), and multi-line parallel progress bars β all rewritten lines across rows are reported together per progress tick. - Smarter rewrite-mode detection β
has_rewrite_control()now only activates rewrite mode for actual terminal-control CSI sequences, not passive styling (colors, decorations). Plain ANSI-styled output stays in line-buffered mode and is emitted on newline boundaries only.
- Plain progress mode is line-buffered β non-rewrite output accumulates until
\n, then emits complete lines viacompleted_linesbuffer. No more mid-line fragmentation in plain-text progress.
- Background shell progress hooks β
on_background_progress()hook delivers incremental stdout/stderr every ~3 seconds while a background command runs.TerminalProgressStatenormalizes rewritten terminal lines (\r,\b, ANSIESC[K) to their latest visible text, so the model sees clean output instead of raw control characters. UTF-8 boundary-safe chunking viacomplete_utf8_prefix_len()prevents splitting multi-byte sequences across progress deliveries. insecure()mode for NativeRuntime β builder option to skipenv_clear(), allowing the shell to inherit host environment variables.
execute_command()extracted as public method onNativeRuntimeβ takes astd::process::Commanddirectly, enabling non-shell invocations through the native runtime.build_shell_command()now returnsstd::process::Command(decoupled from tokio).ToolsSearch::NAMEandToolsSelect::NAMEadded aspub constβ used inname()instead of raw constants for cleaner code.
Executor::temp_dir()removed from the trait β no longer part of the public executor interface.NativeRuntimegains atemp_dir()builder method instead.- Native executor
name()changed from"native_shell"to"shell". tools_searchremoved fromDEFAULT_SKILL_TOOLSβ skill agents no longer receivetools_searchby default.
- Remove deprecated tests β test modules removed from
extractor.rs(106 lines) andgoogle.rs(52 lines), both already marked#[deprecated]since 0.12.0.
- Multi-workspace file tools β
ReadFileTool,EditFileTool,WriteFileTool, andSearchFileToolnow acceptworkspaces: Vec<PathBuf>instead of a single workspace. Context metaworkspace/workspacesfields take precedence over defaults, with automatic fallback.SearchFileTooliterates all workspaces and merges results. Newwith_workspaces()constructor for multi-default setups.
- Workspace-scoped error messages β all filesystem tool errors now include workspace, requested path, and resolved path context for faster debugging. New
workspace_access_error()helper produces consistent "not accessible from any configured workspace" messages. - Note storage limits doubled β
NOTE_CHAR_LIMIT8 KB β 16 KB,NOTE_MATCH_PREVIEW_LIMIT80 β 120 chars.
- Skill cache now correctly updates on successful load (
write().entry().insert_entry).
google,extractormodules marked#[deprecated(since = "0.12.0")]β will be removed in a future release.
- Unified
ToolsOutputβToolsSearchOutputandToolsSelectOutputmerged into singleToolsOutputstruct withVec<FunctionDefinition>(full tool definitions, not just name+description), so the model can invoke tools immediately aftertools_search. IntermediateToolsSearchItemstruct removed;rank_search_itemsreturns names directly, definitions resolved at output boundary. Default limits: search 0β10, select 0β5 with explicitMAXcaps.
- Agent failures as errors β when an agent call fails, return a
ToolOutputerror instead of breaking the conversation, allowing the LLM to correct and recover.
workspacefield removed fromExecArgsβ shell tool no longer accepts aworkspaceparameter; commands always execute in the runtime's workspace directory.join_current_dirhelper removed.
- Shell spawn failures now return structured
ExecOutputerrors instead of propagating as Rust errors, so the model can see and respond to command execution failures.
- Native shell runtime renamed from
"native"to"native_shell"; background shell tasks now return an immediatetask_idoutput so callers can track long-running commands.
- SubAgent extracted as top-level module β
SubAgent/SubAgentSet/SubAgentManagermoved fromcontext::subagentto top-levelcrate::subagent.SubAgentManageris now an Agent (viaAgentSet) instead of a Tool, enabling properAgentHookcallbacks for subagent lifecycle. Session-based background execution withSubSessionstracking, idle timeout (10 min), background task wait (1 hr), and automatic compaction at 81+ turns. - SkillManager reduced to read-only inspector β
SkillManagerno longer supportscreate/patch/edit/delete/write_file/remove_filedisk management operations. Skills are now created and updated by editing files directly on disk via shell or file tools.SkillArgssimplified to{name}only (withdeny_unknown_fields); output is nowSkillContentOutput. - Rename
work_dirβworkspaceacross all tools, runtimes, and context metadata. Shell output field changed fromwork_dirtoworkspace. - Drop legacy DeepSeek client β replaced by multi-label model support (
labels: Vec<String>onModelConfig). - Remove sandbox feature and
boxlitedependency from engine crate. Shell extension now always usesNativeRuntime. - Rename Dyn traits β
AgentDynβDynAgent,ToolDynβDynTool; internal storage switched fromBox<dyn ...>toArc<dyn ...>. - Rename
stepβturnsinCompletionRunnerAPI. - Remove
CompletionHookβ superseded byAgentHookwith background support. - Remove
prune_raw_historypipeline β trait method, 4 provider impls,CompletionRunnermethod, andpruned_placeholderhelper all deprecated since 0.11.0 have been removed. WorkDirrenamed toWorkspaceinExecArgs,ExecOutput, and context metadata.MAX_OUTPUT_BYTESincreased from 128 KB to 256 KB.ModelConfig::model()now returnsResultinstead of silently producing anot_implementedmodel.- Remove deprecated
evaluate_tokens(anda_core) andbuild_model(anda_engine) β superseded byestimate_tokensandmodel().
- Downcast support β
as_any()/into_any()onDynAgentandDynTooltraits withdowncast_ref/downcastconvenience methods for type-safe concrete type recovery. PromptCommandenum withFrom<String>for slash-command parsing (/ping,/command ..., plain text).AgentOutputimprovements βthinkingβthoughtsrename; newsessionfield;PartialAgentOutputstruct;into_tool_output()method.CustomEnvwith auto-inject β shell tool environment variables can be marked asdefaultfor automatic injection; key/description metadata exposed to model providers without leaking values.- Per-agent model selection β sub-agents and call-agents use their configured model from the
modelsmap, falling back to the parent model. CompletionRunner::finalizeβ clean unbound runner completion; steering interrupt; follow-up message consolidation.ConversationDeltaβ offset-based incremental conversation fetching for large conversations.- Per-tool usage tracking β
AgentOutputandCompletionRunnertrack per-toolUsage;ToolOutput.tools_usagefield. - Multi-label model support β
Modelsnow supportsHashMap<String, Vec<Model>>, allowing multiple models per label. - Tool call statistics β per-tool call counts tracked in
CompletionRunner.tool_call_stats. - Safe env vars for native shell β
SAFE_ENV_VARSwhitelist passes only functional host env vars (PATH,HOME,TERM, β¦) to shell commands, never secrets. - Prefix constants β
REMOTE_TOOL_PREFIX(RT_),REMOTE_AGENT_PREFIX(RA_),SUB_AGENT_PREFIX(SA_) defined as constants; prefixing centralized atAgentCtx::definitions(). CompletionRunner::unboundbuilder β enables unconstrained completion execution.EngineReffor late binding β switch toArc<Engine>throughout.- Per-agent storage and
output_schemaforSubAgentβ refactored from single CBOR to individual files per agent. - Multiple steering/follow-up messages queue β
Vec<String>andVecDeque<String>replace singleOption<String>. - Batch conversation retrieval β
batch_get_conversationsfor efficient multi-conversation loading. - Persistent note tool β agent-scoped durable notes with add/replace/remove operations.
- Todo tool β session-scoped task list shared with subagents.
- Strict mode enabled for all tool definitions (
strict: Some(true)). Conversationextra field β extensible metadata for conversations.Thoughtsmethod onMessageβ extract reasoning content.Idlestatus added toConversationStatus.- CWT verification support in engine-server.
- Subagent module extracted from context to top-level with session-based background execution.
- Hook system enhanced β
PrefixedId,on_background_progress,ToolBackgroundHook,DynToolJsonHook. - Model API simplified β
set_model_byβset,get_model_byβget+resolve;Models::from_configshelper. - Remote dispatch β now checks function registration, prefers longest matching handle.
- Native shell runtime β removed shell detection infrastructure; always uses
shon Unix,cmd.exeon Windows. - Defined prefix constants and centralized prefixing to outer layer.
BaseCtxpassed as parameter instead of stored inSubAgentManager.- Tool calls deferred to next turn.
EngineBuilder::empty()made async for proper initialization.- Child contexts clone parent state instead of sharing
Arc<RwLock>. SkillFrontmattermetadata upgraded fromBTreeMap<String, String>toBTreeMap<String, Json>.- Tool errors use JSON
{"error": "..."}formatting instead of plain strings.
- Auto-fill
max_output_tokensfrom model config when caller doesn't set it. - Cap Anthropic max tokens at 64,000.
- Normalize model labels to lowercase for consistent matching.
cache_store_setfix β cache was deleted instead of updated afterstore_put.nullcontent in Message deserialization handled correctly.select_resourcespreserves ordering withO(n)single-pass algorithm.SO_REUSEADDRfallback for platforms withoutSO_REUSEPORT.ToolsSelectOutputdeserialization fixed for nested.contentfield.- All 4 model response parsers now consistently set
output.thoughts.
- anda_engine β all module docs, README rewritten with Install, Quick Start, Core Concepts, Feature Flags, Security.
- anda_core β all module docs, README rewritten with module map, concepts, and minimal
Toolimplementation example.
- Upgrade
inferβinfer2(v0.21),boxliteβ 0.9 (crates.io). - Search file default limit set to 1000.
DEFAULT_SKILL_TOOLSexpanded to includetodo,tools_search,tools_select.- User name max length relaxed from 32 to 96 chars.
SkillFrontmattergainsextrafield withserde(flatten)for forward compatibility.