Changelog
All notable changes to CubePi are documented here.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
0.13.0 - 2026-07-05
Added
- Reasoning capability primitives —
ReasoningProfile,ThinkingControl, andAppliedReasoningControltypes for fine-grained reasoning configuration. Models that support extended thinking (Anthropic extended-thinking API) now expose budget, level, and token tracking through the provider layer. - Reasoning controls render in all providers —
BoundModelstreams now carry reasoning state per-turn. Rendered thinking blocks are available in message content for inspection and tracing. - Run-scoped compaction and real-token triggering —
CompactionMiddlewarecan now compress history within a single long agentic run (previously compression only occurred at run boundaries). Compaction triggers on true context fill (input + cache_read + cache_write from the last turn) instead of cache-blind character estimates, so prompt caching can't mask genuine over-limit context.
Fixed
- Tool-batch fault isolation. One escaping exception in a parallel tool
batch (HitlControlException, CancelledError, or BaseException from
after_tool_call) used to abort the bare-await collection loop, dropping every
ToolResultMessage in the batch — including succeeded siblings — and leaving
dangling tool_calls in the checkpoint. Now
_execute_parallelsettles every task and synthesizes error results for failures, preserving all checkpoint state and emitting events in correct order on suspend/resume. - Parallel HITL approval replay. Fixed answer ledger replay to correctly handle parallel tool batches with mixed approved/pending tool calls. Added explicit answer ledger persistence across all checkpointer backends (sqlite/postgres/mysql) so HITL resume paths have a durable record of prior approvals.
- Checkpointer corruption detection. Load paths now wrap per-row deserialization in CheckpointCorruptionError with thread_id/backend context. Unknown roles are now treated as corruption in all backends instead of silently passing through.
- Reasoning field gating. Fixed rendering of reasoning fields to only
appear when
model.reasoningis enabled, preventing type mismatches on models that don't support extended thinking. - Checkpointer v5 schema support. Restored compatibility with v5 schema migrations across all backends.
0.12.0 - 2026-06-24
Added
CompactionMiddleware(tool_result_compressor=...)— aCallable[[ToolResultMessage], str | None]callback for selective tool result preservation during compaction. Return astrto preserve that text verbatim in the summary (for grounding/citation); returnNoneto fall through to default pruning. Preserved results are appended to the summary as a labeled reference section, excluded from the summarizer input to save budget, and accumulated across compaction rounds viaCompactionStatepersistence.- Sender attribution at the provider boundary.
UserMessage.metadatacan now carrysender_user_id/sender_display_name; providers prefix the first text block with[Name]:when converting to the API format. Keeps stored message content clean while letting the model know who sent each turn in group-chat scenarios. - Deferred tool ordering hint. The dispatcher description now hints
models to emit
tool_namebefore arguments, smoothing streaming UX for dispatch-mode deferred tools.
Fixed
- Removed stale
(latest)labels from Chinese 0.7 and 0.8 version docs.
0.11.0 - 2026-06-17
Changed (BREAKING)
- Deferred tool groups default to the new
dispatchstrategy. Tool schemas are delivered throughload_toolsresults and invoked via thedeferred_tool_calldispatcher; the tools array and system prompt stay byte-stable, so expansions no longer invalidate the prompt cache. Restore the v0.10 behavior withAgent(deferred_tool_strategy="inject")/DeferredToolsMiddleware(strategy="inject"). DeferredToolsMiddleware(resumed_schemas=...)andResumedState.expanded_schemasare removed;prepare_resumed_statetakes a requiredstrategykeyword (a default could silently resume an inject-mode host with hidden tools).- Inject mode no longer renders expanded schemas into the system prompt. The definitions were already in the tools array — the duplicate rendering (double token billing per turn) is gone.
Added
resolve_tool_callmiddleware hook — rewrite a tool call before validation,before_tool_call, execution, events, and tracing see it. Composition is first-non-None-wins. Powers the deferred dispatcher; also usable for tool aliasing/redirection.AgentTool.expose_to_model— whenFalse, the tool is resolvable and executable by the engine but its definition is never sent to the provider. Dispatch-mode deferred tools use this.Agent(deferred_tool_strategy=...)andDeferredToolsMiddleware(strategy=...)— choose"dispatch"(default) or"inject".- Resolved dispatcher calls that fail argument validation get the tool's full schema appended to the error result, so the model can self-correct in one round trip.
Fixed
- HITL resume short-circuit now emits
HitlAnswerEvent. Previously,_await_answer's resume path returned the pre-loaded answer without emitting the event, so subscribers (e.g. IM outbound tailers) never learned the question was answered. - Explicit
resolve_tool_callcomposes with middleware resolvers instead of replacing the chain. An explicit resolver passed toAgent(resolve_tool_call=...)becomes the chain head (first-non-None-wins) rather than silently disabling middleware-provided resolvers like the deferred dispatcher.
0.10.0 - 2026-06-10
Removed (BREAKING)
"minimal"removed fromThinkingLevel.ThinkingLevelnow readsLiteral["off", "low", "medium", "high", "xhigh"]; the.minimalfield is gone fromThinkingBudgets;THINKING_LEVELSno longer contains it; Anthropic's defaultlevel_budgetsand OpenAI Responses'_THINKING_TO_EFFORTno longer map it. Callers that previously passedthinking="minimal"must switch tothinking="low"(or"off"). Rationale: DeepSeek's Anthropic-shape endpoint rejectseffort=minimalonoutput_config, and OpenAI'sreasoning.effortpath rewrote it to"low"downstream anyway — keeping it was a footgun that surfaced as a 400 + fallback.
Added
-
synthetic_user_message(text, *, source) -> UserMessageandis_synthetic_message(message) -> bool— public marker for framework-injected user-role messages. Middleware-injected nudges (todo guard errors, goal continuations, compaction summaries,generate_structuredretry feedback) now stampmetadata["synthetic"] = Trueso downstream UIs can tell internal scaffolding apart from real human input. RealAgent.prompt()/Agent.steer()messages remain unmarked. Closes #171. Exported fromcubepiandcubepi.providers. Use this factory (not bareUserMessage) when returning messages fromTurnAction.inject_messagesoron_run_end. -
DeferredToolGroup/DeferredToolsMiddleware— progressive tool disclosure primitive. Hides MCP tool schemas from the model by default, injecting a compact catalog into the system prompt instead. The model expands groups on demand via the built-inload_toolstool (full or selective). Key properties:- Catalog sorted by
group_idfor byte-stable system prompt prefix. - Expanded schemas append-only (expansion order, never reordered) for prompt-cache prefix stability across turns.
- Loader called once per group per run; selective expansions filter from the cached result.
Agent(deferred_tool_groups=[...])— primary API. Middleware is auto-created internally withextra_refbound toself._extra.- Cross-run replay via
DeferredToolsMiddleware.prepare_resumed_state(), which returns pre-loaded tools, remaining groups, and expanded schemas for prompt-cache continuity. - Exported from
cubepi.deferredasDeferredToolGroup,DeferredToolsMiddleware, andResumedState.
- Catalog sorted by
-
tool_choiceon Provider — newtool_choice: ToolChoice | Noneparameter onBoundModel.stream(),BoundModel.generate(), and theProviderprotocol. Accepts"auto","required","none", or a specific tool name string. Each built-in provider maps the value to its native wire format (Anthropic:{"type": "any"}for"required", OpenAI:"required", etc.).FauxProvideraccepts and ignores the parameter. Type alias:ToolChoice = Literal["auto", "required", "none"] | str, exported fromcubepi.providers.base. -
BoundModel.generate_structured()— tool-based structured output. Pass a PydanticBaseModelsubclass and get a validated instance back:from pydantic import BaseModelclass Sentiment(BaseModel):label: strconfidence: floatresult = await model.generate_structured(Sentiment,messages=[UserMessage(content=[TextContent(text="Great product!")])],)Injects a synthetic tool from the model's JSON schema, forces the call via
tool_choice, and validates the response withoutput_type.model_validate(). Retries on validation failure (configurablemax_retries, default 1). RaisesStructuredOutputErroron no tool call or validation exhaustion. -
GoalMiddleware— autonomous goal-driven agent runs. A separate evaluator model judges whether a/goalcondition has been met after each worker run (dual-model architecture — the agent isn't grading its own homework). Continues until the evaluator confirms ormax_evaluationsis hit. Outcome inagent.state.extra["goal"].from cubepi.middleware.goal import GoalMiddlewaregoal = GoalMiddleware(evaluator=provider.model("claude-haiku-4-5-20251001"),max_evaluations=10,)agent = Agent(model=provider.model("claude-sonnet-4-6"), middleware=[goal])await agent.prompt("/goal all tests pass")Exported from
cubepi.middlewareasGoalMiddleware.
Changed
on_run_endfires on every outer-loop iteration instead of once perprompt()call. The_reflection_firedsingle-fire guard has been removed. Existing middlewares that returnNoneafter one injection are unaffected. This enables evaluation loops likeGoalMiddleware.
Changed
- Internal logging now uses stdlib
loggingexclusively. PreviouslyFallbackBoundModeland the provider listener-exception path tried to importlogurufirst and fell back to stdlib. The loguru path was silently incorrect — loguru does not perform%sargument substitution, so failover warnings rendered literal%splaceholders instead of the resolved labels. cubepi has never declared loguru as a dependency; hosts that prefer loguru should intercept stdlib logging records into it. No public API change.
Fixed
-
FallbackBoundModelfailover log line now substitutes its placeholders. Before the loguru removal above, the WARNING emitted on every failover readfailed=%s → next=%s reason=%s attempt=%s/%sliterally because the loguru-backed logger ignored the positional args. Now renders asfailed=anthropic/claude-opus-4-5 → next=openai/gpt-5 reason=… attempt=1/2. -
Recorder.attach()andMeter.attach()now subscribe to every provider in aFallbackBoundModelchain (closes #167). Previously they only listened tochain[0].provider, so post-failover calls executed againstchain[1..]were invisible to provider-level observability — chat spans, token usage, cache metrics, and cost telemetry were missing for fallback legs. Adds a newcubepi.providers.fallback.chain_providers()helper used by both attach paths to walk and dedupe the chain. Agent-event-driven observability (e.g. cost middleware readingMessageEvent) was already correct and is unchanged.
0.9.0 - 2026-06-08
Added
-
TodoListMiddleware— built-in task-tracking middleware for multi-step agents. Adds awrite_todostool that lets the model maintain a structured checklist (pending/in_progress/completed). Includes:- Finalization guard — if the model delivers a plain-text final response while items remain unfinished, it is looped back once to update the list before the run ends.
- Stale-todo reminder — a soft
UserMessageis injected after several turns without awrite_todoscall, prompting the model to keep the list in sync without blocking. - Parallel-call guard — if the model calls
write_todosmore than once in a single turn, the duplicates are rejected and the checklist is rolled back to its pre-turn state. - State (
todos, guard counters) lives inAgentContext.extraand survives checkpointing. - Constructor:
TodoListMiddleware(extra_ref=..., tool_description=..., system_prompt=...).extra_refmust return the liveAgentContext.extradict (same object, not a copy) so the tool executor can write into it. - Exported from
cubepi.middlewareasTodoListMiddleware,Todo,WriteTodosInput, andTodoGuardBlocked.
-
FallbackBoundModel— built-in failover chain at theBoundModellevel. Wrap an orderedchainofBoundModelinstances; onRateLimited,ProviderUnavailable, orContextLengthExceeded(configurable viatrigger_errors), or on a first-event stream error, the next model in the chain is tried transparently. Optionalon_failovercallback for billing/metrics hooks. Exported fromcubepiandcubepi.providers. -
DEFAULT_TRIGGER_ERRORS—frozenset({RateLimited, ProviderUnavailable, ContextLengthExceeded}). The default set of error types that trigger failover inFallbackBoundModel. -
BoundModel.generate()/BoundModel.stream()— the handle returned byprovider.model(...)now drives a provider call directly. Useful for utilities (summarizers, classifiers) where you already hold aBoundModeland want to skip the agent loop:bound = provider.model("claude-sonnet-4-6")reply = await bound.generate(messages=[UserMessage(content=[TextContent(text="hi")])],system_prompt="Be brief.",)Both methods forward to the bound provider with
model=bound.specand mirror theProvider.generate/Provider.streamsignatures exactly.
Breaking
Middleware.extra_llm_calls()returnsIterable[BoundModel]instead ofIterable[tuple[Provider, Model]]. Third-party middleware overriding this hook must update the return shape (see Migration). The recorder consumer incubepi.tracingwas adapted in lock-step; built-inCompactionMiddlewarealready updated.cubepi.middleware.compaction.summarizer.summarize()takesmodel: BoundModelinstead of separateprovider: Provider, model: Modelkwargs. Direct callers (rare — this is internal toCompactionMiddleware) must wrap the pair. The publicCompactionMiddleware(summary_model=...)API is unchanged.cubepi.run_agent_loopandcubepi.run_agent_loop_continuetakemodel: BoundModelinstead of separateprovider: Provider, model: Modelkwargs. Stateless-loop callers driving the loop outside ofAgentmust update. TheAgentAPI is unchanged — it already tookmodel: BoundModel.
Migration
-
Middleware authors overriding
extra_llm_calls():from cubepi.providers.base import BoundModel# Beforedef extra_llm_calls(self):return [(self._provider, self._model_spec)]# After — either build one explicitly…def extra_llm_calls(self):return [BoundModel(provider=self._provider, spec=self._model_spec)]# …or, if your middleware already holds a BoundModel (recommended),# just return it:def extra_llm_calls(self):return [self._bound_model] -
Direct
summarize()callers (uncommon):# Beforeawait summarize(provider=provider, model=model_spec, ...)# Afterawait summarize(model=BoundModel(provider=provider, spec=model_spec), ...) -
Stateless-loop callers (uncommon — most users build an
Agent):# Beforeawait run_agent_loop(prompts=[...],context=ctx,provider=provider,model=model_spec,convert_to_llm=...,emit=...,)# Afterawait run_agent_loop(prompts=[...],context=ctx,model=provider.model("id", ...),convert_to_llm=...,emit=...,)
Fixed
-
StructuredValuefields now preserveBaseModelpayloads on serialization. Fields typedStructuredValue(theJsonPrimitive | BaseModel | list | dictunion used by tool-resultdetails,AgentToolResult,HitlAnswerEvent.answer, and compaction message-ref hashing) silently serializedBaseModelinstances to{}onmodel_dump(). Pydantic's union dispatch picks the dump schema from the declared base, not the runtime subclass, so the concrete instance's fields were ignored with no error or warning — data gone. Annotating theBaseModelbranch withSerializeAsAny[BaseModel]fixes the silent loss across all five affected sites: checkpointer save, compaction state,ToolExecutionEndEvent,ToolExecutionUpdateEvent, andHitlAnswerEvent. -
SubagentMiddlewarenow strips checkpointed-HITL elements from a child agent's inherited tools / middleware. Previously, passing the parent agent'sask_user_tool(channel)inshared_tools(the common pattern when the host wants tools shared between parent and children) caused the child's firstprompt()to raiseAgent has checkpointed HITL elements bound to run_ids ...because the binding's parentrun_iddidn't match the child's freshrun_id. The middleware now drops any element whose.hitlis a checkpointedHitlBindingbefore constructing the child — the subagent runs autonomously without the parent's HITL channel, matching its "ephemeral and autonomous" design intent. Elements without.hitl, or with non-checkpointed bindings, are inherited as-is.
0.8.0 - 2026-06-06
Added
@tooldecorator —cubepi.toolbuilds anAgentToolfrom a plain async function: the input schema is generated from the typed parameters (honouringField(...)metadata), the docstring becomes the description, and the loop-suppliedtool_call_id/signal/on_updateare injected only when declared. Tools may return astr, aContent, alistof content, or a fullAgentToolResult. The longhandAgentTool(...)remains fully supported.- Conversation fork — fork a thread at a completed-run boundary, or run
a one-shot ephemeral continuation against a snapshot:
Agent.fork(src, new, *, after_run_id, metadata=None)— physical-copy fork at a completed-run boundary.Agent.fork_once(src, message, *, after_run_id) -> ForkOnceResult— single-turn ephemeral continuation, no checkpointer writes.Agent.prompt(message, *, run_id=None) -> strnow accept-or-generates therun_idand returns it.Agent.state.active_run_idexposes the in-flightrun_id.Agent(messages=...)constructor arg for ephemeral pre-seeded history.Checkpointer.snapshot,fork,claim_run,mark_run_complete,load_pendingProtocol methods.cubepi_runstable per backend (Postgres / MySQL schema v3 → v4).Message.run_id: str | Nonefield on all three Message variants.HitlBindingattribute onAgentTool/Middleware;ask_user_toolandApprovalPolicyMiddlewarepopulate it.
Fixed
- Tool results that set
is_error=Trueare now reported as errors. A tool body that returnedAgentToolResult(is_error=True)without raising was surfaced to the model as a successful result; the flag is now honored on the execution path (affects both@tooland longhandAgentTooltools).
Breaking
Agent.prompt()return type changed fromNonetostr. Callers ignoring the return value keep working.CheckpointerProtocol gained 5 new methods. Third-party v3-only checkpointers continue to work for vanillaprompt()via degraded mode; fork APIs raiseCheckpointerErroron such backends.
Migration
- Postgres / MySQL: run the new alembic helper (see backend guides).
- SQLite: auto-migration at connect time.
- Legacy
run_id=NULLmessages remain readable; threads with only such messages are not forkable.
0.7.0 - 2026-06-05
Added
Provider.generate(...)one-shot helper — providers now expose a non-streaming call that consumesstream()and returns the finalAssistantMessage. It supports per-call overrides formax_output_tokens,temperature,thinking, andthinking_budgets.- Compaction middleware —
cubepi.middleware.CompactionMiddlewaresummarizes older conversation turns into JSON-safeAgentContext.extrastate and sends the model a compressed view while preserving full agent history. - Subagent middleware —
cubepi.middleware.SubagentMiddlewareadds asubagenttool that runs an ephemeral childAgent, supports shared tools and middleware inheritance, captures child events, and exposes host callbacks for application-specific event streaming. - Typed provider error taxonomy — built-in providers wrap SDK failures in
ProviderErrorsubclasses such asContextLengthExceeded,RateLimited,ProviderAuthFailed,ProviderUnavailable, andProviderBadRequest. Tracer.oneshot(...)— trace a single background LLM call without a fullAgentloop; the trace CLI can filter these runs via--meta oneshot_operation=....- Checkpointed HITL run IDs — checkpointers now persist the owning
run_idatomically with pending HITL requests so hosts can resume the correct detached run after a restart. on_run_endmiddleware hook — fires exactly once after all turns and tool calls complete, beforeAgentEndEvent. Return alist[Message]to inject additional messages and run one extra model turn (e.g. a memory-reflection pass); returnNoneto do nothing._reflection_firedguard in the loop prevents the injected turn from re-triggeringon_run_end.should_stop_after_turnandturn_action.decision == "stop"paths now route throughon_run_endbefore emittingAgentEndEvent. Error/aborted runs (stop_reason in ("error", "aborted")) skip the hook.- HITL-interrupted runs (HitlDetached / HitlAborted) also skip the hook — the conversation is paused, not finished.
- Compaction summarizer tracing —
CompactionMiddleware's summary call is now first-class in the trace tree. Acubepi.compaction.summarizeparent span (carryingcubepi.compaction.message_count) wraps the summarizer LLM call, and the recorder auto-subscribes the middleware'ssummary_providerso itschatspan lands as a child:Previously summarizer calls bypassed the trace entirely.invoke_agent└── cubepi.turn├── cubepi.compaction.summarize│ └── chat <summary-model>└── chat <main-model> Middleware.providers()protocol — middleware authors can override this method to expose any extraBaseProviderinstances the middleware owns;Recorder.attach()walksagent._middlewareand wires its listener registry on each one (id-deduped againstagent._provider). Default is empty, so existing custom middleware needs no change.
Changed
-
Breaking: agent construction now takes a bound model from
provider.model(...). ReplaceAgent(provider=provider, model=Model(...))withAgent(model=provider.model("model-id", ...)).provider_idnow lives on provider constructors and is copied into model metadata used for tracing, response metadata, and error messages.Tracer.oneshot(...),CompactionMiddleware, andSubagentMiddlewareuse the same bound-model shape. -
Breaking: pre-model middleware hooks now receive
AgentContextdirectly. Update custom middleware and explicit hook callables from the old signatures:transform_context(messages, *, signal=None)transform_system_prompt(system_prompt, *, signal=None)convert_to_llm(messages)
to the new signatures:
transform_context(messages, *, ctx, signal=None)transform_system_prompt(system_prompt, *, ctx, signal=None)convert_to_llm(messages, *, ctx)
Use
ctx.extrafor middleware state that should survive checkpointing. This release does not include old-signature compatibility shims. -
Breaking: custom providers must implement
Provider.generate(...)or inherit fromBaseProvider, which suppliesgenerate()by consumingstream(). -
Breaking: Postgres and MySQL checkpointer schemas are now version 3. Host applications must add the nullable
run_idcolumn tocubepi_threadsand callwrite_schema_version_op()in their Alembic migration before using this release. -
Breaking: the
cubepi.providers.imagessurface has been redesigned to align with the chat-provider 0.7 conventions: providers now takeprovider_idand an optionalImagesCapabilityDescriptor; models are built viaprovider.model("id", ...)(renamedproviderfield toprovider_id, addeddefault_size/n/quality/output_formatandcostmetadata);ImagesContextis typed (size/n/quality/output_format/seed/ negative_prompt/steps/guidance/extra); per-call options live on a newImagesOptionsbag (signal,on_payload,on_response); failures raisecubepi.errors.ProviderErrorsubclasses instead of in-bandAssistantImages.error_message; thecreate_images_provider/register_images_provider_classregistry is removed. The new shape reaches OpenAI, Doubao Seedream, SiliconFlow, and Together AI through a singleOpenAIImagesProviderconfigured with the rightImagesCapabilityDescriptor. -
CI now runs
mypy cubepiin addition to pytest and ruff.
Fixed
- Tool argument
ValidationErrors are formatted as model-readable tool results, including literal and extra-field errors. - Provider stream-level SDK exceptions are classified into typed cubepi errors instead of leaking raw vendor exception types.
Tracer.oneshot()now closes chat spans on failure/cancellation, awaits stream completion and flushes, forwards abort signals, and surfaces silent producer failures.- Anthropic empty-assistant recovery — a persisted
AssistantMessage(content=[], stop_reason="error")(e.g. from a transient provider failure that the checkpointer recorded) no longer poisons the conversation:- Trailing empty assistant turns are dropped before send (the next request regenerates from the preceding user prompt instead of treating the placeholder as an assistant prefill).
- Mid-history empty assistant content is replaced with an
[empty response]text block so the wire payload satisfies the Anthropic API'smessages.N: all messages must have non-empty contentrule. - The trim happens in
stream()before the cache policy resolves breakpoints, so the user's last-messagecache_controlmarker survives on retry paths.
- Tracing root attribution under middleware-driven providers — when a
middleware provider (e.g.
CompactionMiddleware.summary_provider) issues the first chat span of a run before the agent's main call, the rootinvoke_agentspan'sgen_ai.provider.name/cubepi.agent.system_prompt_sha256/cubepi.agent.toolsare no longer overwritten by the middleware's values. The recorder now gates root attribution by model identity (not listener identity), and falls back to first-call-wins when a middleware declares the same(provider, model)pair as the agent's main. - The compaction summarizer's wrapper span now installs
turn_spanas the OTel current span, socubepi.compaction.summarizelands as a child ofcubepi.turninstead of becoming an orphan root.
Removed
- Removed unused
PyYAMLfrom the default dependency set; the core dependency set is back toanthropic,openai, andpydantic.
0.6.0 - 2026-05-31
Added
- Human-in-the-Loop (HITL) — first-class suspend/resume for agent runs that
need a human decision mid-flight:
HitlChannelprotocol withInMemoryChannelandCheckpointedChannelimplementations.CheckpointedChannelpersists pending requests so an agent can be resumed after a process restart.Agent(channel=...)wiring;agent.respond(),agent.detach(), andagent.abort_pending()for external controllers.ApprovalPolicyMiddlewareandConfirmToolCallMiddlewarefor declarative tool-call gating;ask_userbuilt-in tool for open-ended prompts.ScriptedChannelandNoopChannelfor deterministic testing.HitlRequestEvent,HitlAnswerEvent,AgentSuspendedEvent,AgentAbortedEventstream events.- Lazy OTel
hitl.ask/hitl.confirmspans with outcome attributes. - Pending-request persistence on Memory, SQLite, Postgres, and MySQL checkpointers (schema v2 — additive migration, backwards compatible).
MySQLCheckpointer— full-featured MySQL/MariaDB checkpointer with Alembic schema management, matching the Postgres implementation.- Stream recording +
trace convert—record_stream()captures a raw providerMessageStreamto JSONL;cubepi trace convertreplays the recording as a structured trace. Useful for offline debugging and testing without live API calls. - Trace CLI — run-metadata filtering (
--meta/--show-meta): filter and displaytracing_contextkey/value tags attached to a run. - Trace CLI — span IDs in
trace viewnode labels for easier cross-referencing with external OTLP backends. - Agent steering by ID —
agent.steer(...)returns asteer_id; pass it toagent.cancel_steer(steer_id)to cancel a not-yet-drained steering message.
Fixed
- OpenAI provider: deduplicate
finish_reasonprocessing that caused spurious extra events on streamed responses. - Agent:
ToolExecutionStartEventis now deferred until the tool coroutine is actually scheduled, preventing early events for tools that are never run. - Tracing: clear
stream_tool_accumulatedat turn start to avoid stale tool-call data leaking across turns.
0.5.0 - 2026-05-25
Added
- Image generation subsystem (
cubepi.providers.images): a pluggable, per-vendor model interface for image generation, with a class factory so new backends slot in without touching call sites. CapabilityDescriptor: a declarative, per-model description of what a model supports — temperature mode (free / fixed / ignored), reasoning-level mapping (int budget / effort / enum), and themax_tokensfield name. It now drives the OpenAI, OpenAI Responses, Anthropic, and DeepSeek providers, and is exported from the top-level package.cubepi traceCLI (install with thetrace-cliextra): discover, list, view, follow, and aggregate stats over local agent-run traces, with rich rendering and run-id prefix matching.- Tracing: an OTLP exporter and a best-effort
trace()scope helper. The tracing package now imports cleanly withoutopentelemetryinstalled. - Self-describing provider errors that carry provider / model / cause context for easier debugging.
Changed
- Provider reasoning/thinking and temperature handling is now driven by
CapabilityDescriptorinstead of per-provider ad-hoc payload quirks, giving consistent behavior across OpenAI, Anthropic, and DeepSeek.
Fixed
- Anthropic: merge parallel tool results into a single user message; carry
parsed tool arguments through the streaming
toolcall_endevent; computemax_tokensfrom the actual capability budget; honor per-requestthinking_budgetsoverrides. - Agent loop / steering: drain steering at the turn boundary;
after_model_responsenow injects after tool results; backfill tool results for tool calls orphaned by a cancel. - DeepSeek: correct reasoning-effort path and temperature range handling.
Earlier releases
- 0.4.0 - 2026-05-19 — see the release notes.
- 0.3.0 - 2026-05-14 — see the release notes.
- 0.2.0 - 2026-05-10 — see the release notes.
- 0.1.0 - 2026-05-09 — initial release. See the release notes.