Repository navigation
Releases: ApodexAI/AgentCore
Release list
AgentCore 0.18.2
Fixed
- Trajectory JSON snapshots now follow the loop's actual conversation history, including capped and blocked native tool calls, synthetic replies, post-processed result bodies, observer injections, rollbacks and compaction. Awaited trajectory hooks prevent immediate invalid-call results from appearing before their assistant event.
- Assistant messages preserve the normalizer's reasoning policy and native content blocks. Consumers that need flattened text should use
agent_core.messages.text_of(message["content"]). JSONL continues to retain execution events and original tool outputs, including events later removed from conversation history. - Routine snapshots reuse the spool prefix by reference. Nested in-place edits are checked against redacted rendered lines after compactor runs, compaction/rollback notices, and on completion or cancellation; inline image base64 is never hashed. Unannounced nested edits by other observers appear at the next deep check or final snapshot. Image redaction, configured body limits and coalesced atomic writes remain in place.
- Diagnostic annotations follow a copied assistant only when it shares mutable content/tool-call containers or blocks with exactly one vanished source and has no competing copy. Cached strings cannot transfer another turn's usage or stop reason. Ambiguous, deep-copied and scalar-only copied messages retain their diagnostics in JSONL when enabled; direct identity bindings remain intact.
- Cyclic or otherwise unencodable host metadata preserves the message's position with
trace_errorinstead of freezing later snapshots. Source deduplication uses object identity without recursively comparing message dictionaries. Completion and cancellation force the latest snapshot, and a cancellation after completion preserves the terminal snapshot.
AgentCore 0.18.1
Fixed
OpenAIResponsesClient.streamnow streams function-call arguments and returns the completed response's verbatim block list (encrypted_contentreasoning + function calls in provider order), so a streamed Responses turn replays exactly like the non-streaming one; previously streaming dropped every tool call and reasoning item. Argument deltas still provide progress when a gateway omitsresponse.output_item.added, while the completed response controls tool execution if streamed fragments are missing or divergent.responsesis removed fromUNVERIFIED_STREAM_PROTOCOLS, so it now streams automatically when an observer or the reasoning-only watchdog asks for deltas.
AgentCore 0.18.0
Fixed
- Compaction now counts
reasoning_contentin loop and middleware token estimates and drops it from older assistant turns (Tier 1 keeps at least the latest turn, or the lastkeep_tool_resultturns when greater than one; tool-result compression and the Tier 2 input keep only the latest). Longthinking_format: reasoning_contentruns previously looked ~19x smaller than their real prompts, so compaction rarely fired; expect compaction to trigger earlier and prompts to shrink on those profiles. Proxy compatibility was verified with llm-hub deepseek-flash; the self-hosted DSV4 checkpoint and long-task quality were not tested.
AgentCore 0.17.0
Added
-
Reasoning-runaway recovery now works on the native Anthropic adapter. Its retry ladder applies
ThinkingRetryOverride.reasoning_efforttooutput_config.effortwithout sending unsupportedthinking={"type": "disabled"}to adaptive-only models.When all call-level retries fail, the loop gives a reasoning-only response one bounded recovery attempt instead of ending it as
no_tool. This applies both to completions that exhaust the output cap and to streams stopped early by the reasoning watchdog, whose terminal usage and finish reason may never arrive. Recovery remains available on the final allowed turn without consuming an additional logical turn. If recovery also fails, the loop stops withstop_reason="reasoning_runaway"; AgentBus fan-in treats that reason as an incomplete report. Consumers that enumerate stop reasons should add it.
AgentCore 0.16.1
Fixed
- Runaway retry no longer expands the output cap past the model's limit when the client is driven through an
LLMFallbackChain. The chain binds onemax_tokensfor whichever leg serves the request, so expansion is now clamped to the smallest declared output limit among its legs instead of reading acapabilitiesattribute the chain does not expose; a chain whose legs declare no limit keeps the previous context-bounded behaviour.
AgentCore 0.16.0
Changed
- The opportunistic recovery for an invalid streamed tool call now retries with streaming instead of a non-streaming
chatreplay, so the retry's attempt events carryrecovery_action="retry_streaming"instead ofreplay_non_streaming. Calls that match the previous empty-required-arguments condition keep thestream_empty_tool_arguments/stream_empty_args_replayreasons and thestream_empty_args_*response metadata; newly detected cases (invalid JSON or a non-object for tools without required fields) usestream_invalid_tool_call. When a native call is still invalid after the retry, the attempt finishes asaccepted_degradedwith reasoninvalid_tool_calland the call returns an error tool result (error_kind="invalid_arguments") instead of running the tool. Consumers asserting the old replay transport, its event names, oracceptedfor such attempts should update those assertions; setLoopConfig(tool_argument_validation="off")to keep only the legacy retry condition. AgentCore now depends onjsonschema>=4.20,<5.
Added
- Streaming can now be selected independently of token observers (
LoopConfig.stream_transport,call_llm(stream=...)). Native tool calls are checked before execution according to the newLoopConfig.tool_argument_validation("structural"by default,"strict"for full JSON Schema,"off"). Invalid streamed calls are retried once using streaming, and invalid native calls that remain return an explicit tool error instead of running. Blank arguments count as{}, text-mode calls and tool-side type coercion are unaffected, malformed tool schemas never fail a call, and native calls missing an id get a generated one instead of breaking history replay.
AgentCore 0.15.0
Changed
-
Empty completion recovery now runs inside one logical
call_llminvocation in both chat and streaming modes.empty_completion_max_retries(default 2, also available to direct callers) supplies same-leg resamples independently of the genericmax_retriesallowance; the original logical/wall deadlines bound backoff and native fallback advances. Empty attempts are emitted as discarded/failed with reasonempty_completion, including separately identified failed tool-argument replays, and the loop stops withempty_completionafter terminal exhaustion. Native and nestedLLMFallbackChainrouting uses independent per-call cursors, preserves bindings and middleware, respects trigger barriers, and supports an explicitempty_completiontrigger. Direct fallback streams buffer candidate-empty deltas until a real signal commits the leg, preventing discarded tool-argument fragments from contaminating a fallback response. The cooldown wrapper detects blanks before applying its existing policy. Consumers relying on streaming retry counts, stop/error reasons or accepted-attempt metrics should adopt these unified semantics.LLMResponseandStreamDeltagain optionalusage_sourceprovenance; adapters mark provider reports, while wrappers should mark synthetic counts asestimatedor retainestimated: truein usage maps. Reported zero usage remains distinct from estimates. OpenAI Chat/Responses refusal fields and events are preserved, and explicit refusals/content filters now terminate under their own stop reasons even with a nudge policy, without executing accompanying tool calls. Product wrappers opting into native delayed-assembly failover must explicitly implementfor_logical_callandadvance_empty_completionwhile preserving themselves; dynamic attribute forwarding alone is not opt-in.Empty OpenAI refusal placeholders beside text, tool calls or reasoning are ignored. Streams infer an otherwise empty refusal only at normal EOF, including clean closes without a finish-reason chunk; exceptions and cancellation preserve their original failure semantics.
Streamed middleware now receives terminal usage/provenance, provider/model and rejection metadata plus named native tool calls. This restores streamed token/cost accounting and loop detection. Estimates stay diagnostic and do not charge real cost, task budgets or billing aggregates; canonical cache read/write fields preserve zeros and cache-only reported calls. Rate correction handles zero estimates and real zero usage against each limiter's actual capped reservation, while unknown/estimated usage keeps the reservation. Failed or consumer-closed tool proposals are excluded from repeat detection even when their authentic reported usage is accounted. Reported streaming usage/cost and budget consumption will increase from the old undercount; downstream budgets/alerts should be recalibrated. The legacy CostSink signature remains unchanged; cache-aware aggregators receive the separate read/write fields.
Fixed
- Retry Anthropic and OpenAI-compatible streams that close before a completion signal, preventing partial responses from silently ending an agent run. Truncated streams are classified
truncated_stream: resampled on the same key, or advanced to the next provider when a fallback chain is active. The Chat Completions[DONE]check reads decoded bytes, so gzip/deflate-encoded event streams are handled. Responseserrorevents now surface their code and message.AGENT_CORE_STREAM_REQUIRE_TERMINATOR=0accepts unterminated streams with a warning, for gateways that never send a terminator.
AgentCore 0.14.2
Fixed
-
A streamed call that completes with nothing in it — no visible text, no tool call, no reasoning and no usage — now raises
LLMEmptyCompletioninstead of being assembled into a valid emptyLLMResponse.is_empty_completionhas always named "an empty stream" among the cases it recovers, routing it throughis_retriable_with_fallbackto a same-key resample and then a chain advance; the raise is what was missing, so the blank response reached the loop as an answer. Because a reply with no tool call is shaped exactly like "the model chose to stop talking", underno_tool_behavior="stop"it ENDED THE RUN and recordedstopped_by="no_tool", which reads as a clean finish while discarding every turn of work already done. Measured on ApodexHarness's 2026-10-06 GDPval batch over llm-hub, 10 of 19 streamed trials died this way — six within five minutes, one on turn 1 after 37s — with usage absent and backfilled asestimatedzeros. The new error's wording matches the existing_EMPTY_COMPLETION_PATTERNS, so classification and retry need no change, andLLMEmptyCompletionis exported fromagent_core.errors.The predicate is deliberately "nothing whatsoever", not "no visible text": a real 0-token reply still reports usage and a reasoning-only stream is the runaway guard's business, so both keep flowing to the handlers that already reason about them. One behaviour change follows for consumers: a stream whose ONLY content was a tool-call delta that never received a function name used to return an empty response after dropping the unusable call (
test_llm_runtime_nameless_tool_calls), and now raises. That module's own docstring described the remaining half of the failure — the run "ends looking like an ordinary empty submission rather than the infrastructure fault it is" — which this completes.As a second line,
run_agent_loopno longer lets a blank reply reach the no-tool exit at all: a response with no tool call, no text and no usage is resampled up toLoopConfig.empty_completion_max_retries(new, default 2) without spending a logical turn, and if it stays blank the run stops under its ownstopped_by="empty_completion"rather thanno_tool. This covers what the stream guard cannot see — a non-streamed reply, or one a product wrapper rebuilt. Consumers readingstopped_byshould expect the new value; those that treat any non-emptystopped_bygenerically need no change.Empty-response resampling now checks the original response with the same predicate as stream assembly, before history insertion and response observers. Reasoning, opaque signed blocks, usage (including reported zeros), and structured refusal details are preserved. Blank and whitespace-only replies never enter retry history, and a non-empty response resets the consecutive-empty retry budget so separate failures each get their own recovery allowance.
AgentCore 0.14.1
Fixed
-
Anthropic streaming now forwards actual HTTP activity, including SSE heartbeats that the SDK filters out, as empty progress deltas. Healthy requests using omitted thinking can exceed the stream stall window while retaining their configured total call timeout. Silent connections still trigger the stall guard, and cancellation closes both the response and the event-reader task. No configuration migration is required.
-
Bound LLM admission waits, tracing and cancellation cleanup, and enforce total summary request deadlines so stalled hooks, transports or slow response bytes cannot indefinitely hold a call. Concurrency-gate admission is bounded by
LLM_GATE_WAIT_S(disabled by default; logical/wall deadlines still apply) without shrinking the generation budget, ends with reasongate_waiton explicit admission expiry without re-queueing or spending provider retries, and a gate slot is returned only after an abandoned provider request or stream close settles. Streamedafter_llmhooks get a per-hook grace period; background observer drain at loop end is bounded.SummaryLLMEnginegainstotal_timeout(defaultrequest_timeout) and raisesSummaryRequestTimeout, anhttpx.TimeoutExceptionandTimeoutError. Bounded closes finalize async generators in a helper task, so provider/LLM wrapper generators must not hold anasyncio.timeoutor anyio cancel scope across ayield.Middleware and fallback stream wrappers explicitly close their delegated streams before leaving the admission lease, retaining the slot until abandoned provider cleanup settles even when middleware truncates a stream.
-
Preserve OpenAI-compatible Chat Completions and Responses streaming transport heartbeats through SDK filtering, preventing false stall timeouts during silent reasoning while retaining total deadlines, cleanup, retry and fallback semantics. Transport heartbeats no longer satisfy an armed first-chunk (TTFT) bound or count toward
chunks_seen; they extend the inter-chunk stall window only after real output, or when no first-chunk bound is set. Unrecognised response byte streams are left unobserved instead of failing, and heartbeats are no longer emitted alongside every parsed event.
AgentCore 0.14.0
Added
-
AnthropicClientandbuild_protocol_clientacceptprompt_cache_ttl(5m, the API default, or1h), which sets the lifetime of both prompt-cache breakpoints the adapter places. Blank and5momit the field, so an unconfigured consumer's requests are byte-identical to before;ANTHROPIC_PROMPT_CACHE_TTLprovides a deployment-wide default for construction sites a profile does not reach, and a client's own value outranks it. Unsupported values (30m, numbers, a stray unit) raiseValueErrorat construction rather than silently reverting to five minutes, which would only surface as a cache-hit-rate regression. The existingANTHROPIC_PROMPT_CACHE=0kill switch still wins, and 1h writes were already counted by the adapter's cache-write accounting.Consumers should set this per model, not globally: an hour costs 2x base input to write against 1.25x for five minutes, and it pays off only when one TURN can take longer than the shorter window — a cached entry expires relative to its last use. In ApodexHarness's 2026-10-05 GDPval batch, 12.9% of
claude-opus-5-5turn gaps ateffort=maxexceeded 300s and those calls missed the cache 35.7% of the time against 9.5% for the rest, rewriting a median 81k tokens each; a fast model has no such rewrites to avoid and would only pay the higher write rate. A gateway has to forward the field: llm-hub was verified on 2026-10-05 (HTTP 200, write billed to the 1h bucket, a read at 548s still hitting while the 5m control had expired), and theextended-cache-ttl-2025-04-11beta header was not required. -
LoopConfig.stream_llm_tokens(Noneby default) overrides the loop's own choice of transport:Truestreams the turn,Falsedoes not,Nonekeeps the existing automatic selection (the reasoning-only watchdog or an observer'swants_llm_delta). It exists because the transport can be the reason to stream rather than any observer — a gateway that abandons a non-streaming request while waiting for response headers leaves no alternative, and for native Anthropic those headers arrive only when generation finishes. Measured on llm-hub's "Claude Code" channel on 2026-10-05, an identicalclaude-opus-5-5request ateffort=maxreturned502 upstream_unreachable — first byte timeoutafter 76s non-streaming and HTTP 200 when streamed (first byte 3.9s, generation 242s); concurrency was ruled out.Behaviour change in automatic mode:
anthropicnow streams when deltas are requested, where all three native protocols were previously suppressed unconditionally. That suppression predated, by one day, the provider substrate work that madeAnthropicClient.streamrebuild the verbatim block list (trailingsignature_delta,redacted_thinking) so a streamed turn replays like its non-streaming twin —test_anthropic_latest_models.pyhas covered it parametrized over streaming ever since.responsesandbedrockkeep the old behaviour asUNVERIFIED_STREAM_PROTOCOLS(exported) until a test proves their streamed replay; removing one from that set is that test's job. Consumers whose Anthropic-protocol profiles setreasoning_only_*or register awants_llm_deltaobserver will now actually stream — their client must implementstream, since there is no fallback tochat.The suppression is also no longer silent: when automatic mode drops deltas something asked for, the loop warns. ApodexHarness's
tests/contract/test_streaming_deltas.pydocuments the old selection expression verbatim in its docstring and should be updated when the pin moves.