Changelog¶
All notable changes to Arcana will be documented in this file.
[Unreleased]¶
[1.1.0] - 2026-07-07¶
Added¶
RuntimeConfig.trace_include_turn_contentadds an explicit opt-in for storing raw V2 turn content in trace events. The default isFalse.arcana.run(..., auto_route=...)andRuntime.run(..., auto_route=...)now carry the routing choice all the way into the V2 conversation engine.@arcana.toolschema inference now handles more Python typing shapes, including common generics,Literal,Enum, and Pydantic model arguments.
Changed¶
- V2
turntrace events minimize raw content by default. Assistant text, thinking, tool arguments, final answers, and provider metadata are omitted unless raw turn capture is explicitly enabled; traces keep length metadata and stable SHA-256 digests for audit correlation. dev_mode=Truenow enables both prompt snapshots and raw turn-content capture, making the privacy boundary explicit for local debugging.- V2 direct-answer routing now requires a confidence threshold before taking the direct path, reducing accidental bypass of the conversation engine.
Migration¶
- Production users should leave
trace_include_turn_content=Falseunless they have a controlled retention, privacy, and legal-discovery policy for raw agent transcripts. - Trace consumers that previously read raw fields from
turn.metadata["facts"]orturn.metadata["assessment"]should handle minimized fields as nullable and use the accompanying digest fields when correlating evidence. - To restore the old raw-turn capture behavior for a controlled run, set
RuntimeConfig(trace_include_turn_content=True)or run withdev_mode=True.
Removed (internal-not-stable surface)¶
arcana.multi_agent.team.TeamOrchestrator,arcana.multi_agent.team.RoleConfig, andarcana.multi_agent.message_bus.MessageBusare deleted. All three encoded a framework-prescribed Planner→Executor→Critic topology via thearcana.contracts.trace.AgentRoleenum, which Constitution Amendment 3 (v3.4, 2026-05-03) names as a Principle 8 violation under the multi-agent OS framing.
Also removed: the orphaned arcana.contracts.multi_agent.AgentMessage, CollaborationSession, and HandoffResult (only consumers were the deleted classes), and the tests/test_multi_agent.py suite (only covered the deleted classes).
Migration: use runtime.collaborate() and have your code drive the planner→executor→critic loop directly. Each agent gets a pool.add(name=..., system=...) call; turn order, handoffs, and stop conditions live in your loop, not in framework defaults. The migration recipe is in docs/guide/multi-agent.md. For role-addressed pub/sub, replace MessageBus with arcana.multi_agent.channel.Channel (name-addressed, same primitive shape).
Why the deprecation cycle was zero-window: arcana.multi_agent.* is in the internal — not stable tier per specs/v1.0.0-stability.md §2 — no semver promise was broken. The deprecation DeprecationWarning (added earlier in the same release) and the physical removal land together; the deprecated classes were never published to PyPI in any v1.x release, so no user could have observed the warning without also seeing the removal in the same minute. Constitution Chapter VI's "minimum one minor with DeprecationWarning" rule applies to stable surface; here it would have been ceremony, not protection.
-
MessageType.PLAN/RESULT/FEEDBACK/HANDOFF/ESCALATEenum members stay for now — they were tightly coupled to the deleted classes but are part ofarcana.contracts.multi_agent, which still hosts the liveChannelMessage. A future cleanup may reduce the enum, but doing it in this release would touch a contract type the liveChanneldepends on. Not constitutionally interesting; left as housekeeping. -
arcana.contracts.trace.AgentRolestays. It is reachable throughTraceEvent.role(a field on the stableTraceEventname; type changes there are stable-surface breaks perspecs/v1.0.0-stability.md§5). The enum is now documented as vestigial —PLANNER/EXECUTOR/CRITICare kept only so historical trace files keep parsing — and replacement of the field with a free-formagent_name: stris queued for v2.0.
[1.0.0] - 2026-05-01 — "Stable Public Surface"¶
The first release with a binding stability promise. From this point on, the names enumerated in specs/v1.0.0-stability.md §1 follow strict semver; breaks require a major bump and a deprecation cycle (see Constitution Chapter VI and docs/guide/stability.md).
The cut commit also removes the multi-agent surface that the constitution rejected. There is no behavior change for code already on runtime.collaborate().
BREAKING CHANGES¶
Removed: Runtime.team(), TeamResult, and the mode="shared" / mode="session" machinery¶
Runtime.team() was deprecated in v0.8.0 (2026-04-19) with a DeprecationWarning and is physically removed in v1.0.0 — no shim, no alias from team to collaborate. The migration recipe lives at docs/guide/multi-agent.md → Migration from runtime.team().
What is gone:
Runtime.team(goal, agents, *, max_rounds, budget, mode)methodTeamResultPydantic model + thearcana.TeamResultre-export- The
mode="shared"andmode="session"parameters and their internal round-counter / fixed turn-order scheduler team()'s framework-level treatment of[DONE]as a hard stop signal — i.e. theif "[done]" in agent_text.lower(): return TeamResult(...)early-return insideteam(). The[DONE]convention (an agent emitting[DONE]to mean "I am satisfied") is not removed; it remains a perfectly valid pattern in user-written critic loops, V1 agent output, and eval harnesses. What is gone is the framework treating it as a control-flow contract on the user's behalf
Why physical removal, not a shim: team(mode="shared") baked a framework-prescribed collaboration topology — internal round counter, fixed turn sequence, framework-owned context baton — into the runtime. Under Principle 4 (Strategy Leaps), Principle 6 (OS not Form Engine), and Principle 8 (amended in specs/constitution-amendment-2-collaboration-means.md), that shape is not something the framework may prescribe. A shim would either keep the violation reachable behind a thin wrapper or silently change behavior. See specs/v1.0.0-removals.md for the full argument.
Migration:
# Old (removed in v1.0.0)
result = await runtime.team(
"Write a blog post about X",
agents=[
AgentConfig(name="researcher", prompt="..."),
AgentConfig(name="writer", prompt="..."),
],
mode="shared",
)
# New
async with runtime.collaborate() as pool:
researcher = pool.add("researcher", system="...")
writer = pool.add("writer", system="...")
findings = await researcher.send("Research X")
post = await writer.send(f"Write a post using:\n{findings.content}")
AgentConfig is kept on the public surface as a serializable agent description struct (name, prompt, optional model/provider override). It is no longer team-bound — expand it into pool.add(...) keyword arguments when collaborating.
Added — Stability commitments¶
- Constitution v3.3 Chapter VI (Stability Commitments) — the v1.0.0+ semver and deprecation rules are now binding constitutional commitments alongside the Four Prohibitions and Nine Principles.
CONTRIBUTING.mdStability & Versioning section — the rules a PR author needs (deprecation cycle, removals tracker, CHANGELOG migration entry) at the contributor entry point.
Notes for maintainers¶
tests/test_stability_surface.py(14 cases) is the CI guard for the public surface. It still passes after this release. Any future change to a stable name must update this test in lockstep.- Pending removals for the next major bump are tracked in
specs/vX.0.0-removals.md(no entries yet).
[0.10.0] - 2026-04-27 — "Pre-v1.0.0 Stability"¶
All items in this release are non-breaking and trace back to specs/v1.0.0-stability.md. They round out the public surface and migration scaffolding ahead of v1.0.0. No deprecations.
Added — ChatSession.seed_history() (§3.1)¶
ChatSession.seed_history(messages) injects prior conversation
messages into a session at cold start — restoring a chatbot from
external storage without forcing user code through the private
session._messages attribute. Replaces the constitutional smell that
the existing ChatSession.history is read-only (dict copy) and there
was no public mutator: users were required to break encapsulation
to do something every chatbot needs.
- Accepts
Messageinstances (canonical) or{"role": str, "content": str}dicts (convenience). Validates roles againstMessageRole; rejects empty content and unknown types. - System-role entries in the seed are skipped — the session's
system prompt is owned by the constructor (
system_prompt=...). - Does not increment
turn_count(those count turns this session executed; seed is pre-existing history). - Calling twice extends — idempotency is the user's discipline.
- Allowed before or after
send()— but the user owns the timing call; mid-stream seeding may collide with active compression state. - Emits the new
EventType.HISTORY_SEEDEDtrace event withseed_count,role_counts,skipped_system,content_digest(16-char canonical hash), andmessage_count_after. Auditability (Principle 5) is restored — the seed is not invisible.
Added — EventType.HISTORY_SEEDED¶
New arcana.contracts.trace.EventType member for session-lifecycle
audit. Backward compatible: existing event consumers ignore unknown
event types.
Added — arcana.Message / arcana.MessageRole top-level (§3.2)¶
Message and MessageRole are now re-exported at the top of the
arcana package, alongside Runtime, Budget, ChatSession, etc.
The canonical definitions stay in arcana.contracts.llm. The
top-level form is the preferred user import:
# Preferred:
import arcana
msg = arcana.Message(role=arcana.MessageRole.USER, content="hi")
# Also supported (canonical):
from arcana.contracts.llm import Message, MessageRole
# Discouraged (works in CPython today, no stability promise):
from arcana.runtime.conversation import Message # internal import
Cleanup — arcana.runtime.conversation.__all__ (§3.2)¶
arcana.runtime.conversation now declares __all__ = ["ConversationAgent"].
The module's docstring explicitly notes that Message / MessageRole
are imported there for internal use only and are not part of its
public surface. Explicit imports (from arcana.runtime.conversation
import Message) still work for backward compatibility but are no
longer advertised; from arcana.runtime.conversation import * will
now only yield ConversationAgent.
Added — docs/guide/stability.md (§3.3)¶
User-facing distillation of specs/v1.0.0-stability.md §1–2. Tells
users which Arcana imports are stability-promised and which are not,
includes the asymmetric cases (e.g. arcana.contracts.* is stable
under a sub-package; arcana.runtime.conversation.Message is not
stable even though it works today), summarises the post-v1.0.0
versioning rules, and explains the deprecation policy.
External feedback (Roboot, 2026-04) flagged that "what is stable" was
only knowable by reading source. This guide closes that loop. Linked
in mkdocs.yml nav under Guide → API Stability.
Fixed — arcana.__version__ drift¶
arcana.__version__ was last updated at v0.3.1 and silently drifted
through six releases while pyproject.toml was correctly bumped each
time. import arcana; print(arcana.__version__) now returns the
current version.
Fixed — ChatSession.turn_count / max_history were docs-only¶
docs/guide/stability.md and the ChatSession.seed_history docstring
both referenced session.turn_count and session.max_history as
public surface, but neither was a real attribute — only the private
_turn_count / _max_history existed. Discovered during a §3.3
post-merge audit (any new feature should round-trip its own docs).
Added both as read-only properties wrapping the existing internal
state. Additive, no user could have been affected.
Added — Provider tool-calling hints (§3.5a + §3.5b)¶
A user-controlled slot for "extra prompt scaffolding when this provider is invoked with tools bound". Implements the constitutional middle path between "framework auto-rewrites prompts per provider" (violation: Principle 4 — framework deciding how to talk to the LLM) and "users re-discover the same workarounds across every project" (the original feedback's pain).
Decomposed into infrastructure (3.5a, code) and content (3.5b, docs):
3.5a — RuntimeConfig.tool_calling_hint{,s} slot¶
RuntimeConfig.tool_calling_hint: str | None = None— global defaultRuntimeConfig.tool_calling_hints: dict[str, str] = {}— per-provider override- Resolution at request time: per-provider value wins over global. If neither resolves, no hint is injected.
- The hint is rendered as an additional system message, inserted
after the user's authored system prompt(s) and before user/assistant
turns. The user's original
system_promptis never mutated. - Only rendered when
active_toolsis non-empty for the request — if the LLM is invoked without tools, the hint is a no-op. - Fully captured in
PromptSnapshotwhen trace snapshots are enabled (Principle 5: auditable). No new event type added — existing snapshot machinery covers it. - Plumbed from
RuntimeConfigthroughRuntime/ChatSessiontoConversationAgentat all three construction sites (run / chat / chain). - Default empty: zero behaviour change for existing users.
3.5b — docs/guide/providers.md "Tool-Calling Hints" section¶
Per-provider observed quirks plus suggested hint text users can copy
into their tool_calling_hints. Updated as docs (not code), so
recommendation changes never become silent runtime behaviour changes.
Explicitly notes that GLM-4-flash benefits from a hint (the original
Roboot feedback case); OpenAI / Anthropic / DeepSeek / Gemini / Kimi
generally do not need one.
Constitutional rationale¶
- Principle 4 (Strategy Leaps): framework provides plumbing (the slot
- the rendering rule); user owns content. No framework opinion on prompt strategy ships in code.
- Principle 5 (Auditability): the injection is visible in
PromptSnapshot— fully traceable. - Principle 6 (OS not Form Engine): a typed slot for an additional system block is OS-shaped, not Form-shaped (no prescribed reasoning loop, no compulsory hint).
- Prohibition 1 (No Premature Structuring): default-empty means no behaviour change for anyone who doesn't opt in. The slot was added in response to a real, observed need (Roboot integration, 2026-04), not a speculative one.
- The framework explicitly ships no per-provider defaults — default values would themselves be a position on prompt strategy and would drift silently across versions.
16 new tests in tests/test_tool_calling_hint.py cover: no-op cases
(no tools / no hint / wrong provider / empty string), injection
(global / per-provider / both / cross-provider fallback), insertion
order (after leading system blocks / at start when no leading system /
original prompt unchanged), the "no framework default" invariant
across six providers, and the RuntimeConfig plumbing path.
Added — tests/test_stability_surface.py (CI guard)¶
The same import-everything audit that caught DiagnosticBrief /
ContextBudget and ChatSession.turn_count is now a pytest module
with 14 parametrized cases. Every name listed in
docs/guide/stability.md round-trips through hasattr /
importlib.import_module. Includes:
- Top-level
arcana.*(Runtime / Budget / Message / etc.) arcana.__version__is set and looks currentRuntimemethods (run / chat / chain / collaborate / ...)ChatSessionpublic surface — two-directional: documented names must exist AND every public attribute must be documented (catches accidental public exposure of internals)- Each
arcana.contracts.*module's claimed name set - §3.2 invariant:
arcana.runtime.conversation.__all__excludesMessage/MessageRole
If anyone removes a stable name or adds public surface without updating the docs, the test fails before the PR can land.
Added — PR template "Public Surface Impact" section (§3.4)¶
Every PR now answers: does it touch a name on the stability list, and
if so is the change additive or breaking? Breaking changes require a
Migration section in the CHANGELOG entry per the v0.9.0 precedent.
Non-breaking additive changes are tagged "minor bump candidate" so
the release roll-up captures them. Encodes the practice that started
with v0.9.0's ToolErrorCategory migration recipe; no longer relies
on the contributor remembering.
[0.9.0] - 2026-04-26 — "The Tool Boundary Release"¶
Two changes that together turn Prohibition 4 (No Mechanical Retry) and Principle 6 (Runtime is an OS, not a Form Engine) from advisory into runtime-enforced. The tool error contract no longer lets tools self-report "is_retryable=True"; the gateway no longer schedules write tools concurrently.
Both are breaking on user code that imported the old ErrorType enum or
read ToolError.error_type. Migration recipe is at the bottom of this
section.
Changed — Tool error contract (BREAKING)¶
arcana.contracts.tool.ErrorType→ToolErrorCategory— renamed and re-purposed. The old binaryRETRYABLE/NON_RETRYABLE/REQUIRES_HUMANaxis conflated retry policy with error semantics; tools self-reported retry-eligibility and the gateway trusted them. The new categories are structural —TRANSPORT,TIMEOUT,RATE_LIMIT(the three retry-eligible) plusVALIDATION,PERMISSION,LOGIC,CONFIRMATION_REQUIRED,UNEXPECTED. Retry eligibility lives in a single frozenset inarcana.contracts.tool, not in tool code.ToolError.error_type→ToolError.category— field rename.is_retryablebecomes a derived property keyed on the retry-eligible frozenset; tools cannot opt themselves into retry.ToolSpec.max_retriesdefault3→2— one retry buys forgiveness for flap; more starts to mask real problems. Passmax_retries=3explicitly if you depended on the old default.
Changed — Tool execution dispatch¶
ToolGateway.call_many_concurrentnow batches bySideEffect— read-side tools (SideEffect.READ/SideEffect.PURE) run concurrently as before. Write-side tools (SideEffect.WRITE/SideEffect.NETWORK_WRITE) serialize. The runtime owns dispatch semantics at the gateway boundary instead of asking tool authors not to race each other.
Added — Constitutional invariant tests¶
tests/test_constitutional_invariants.py— 13 tests covering the side-effect dispatch contract,ask_usernon-blocking, cognitive- primitive opt-in, structured-output / tool coexistence, and the No-Mechanical-Retry contract. These are runtime-level enforcement of the Prohibition list; if any of them fail, the runtime is no longer constitutional.
Added — Strict typing gate¶
mypy --strict src/is now a CI gate — full strict mode passes. Runuv run mypy src/locally; CI blocks PRs that introduceAnyleakage or untyped surfaces.
Docs¶
docs/architecture.mdrewritten for V2ConversationAgent. The V1- centric narrative is archived underdocs/legacy/.CONSTITUTION.mdv3.2 — corrects principle count (8 → 9 after v3.0 added Principle 9) and tightens cognitive-primitive scope language..github/pull_request_template.md— per-PR constitutional checklist.docs/guide/api-tiers.md— overview of the run / chat / chain / collaborate / batch tier.
Migration¶
from arcana.contracts.tool import ErrorType→from arcana.contracts.tool import ToolErrorCategorytool_error.error_type→tool_error.categoryToolError(error_type=ErrorType.RETRYABLE)→ pick a real category, most likelyToolErrorCategory.TRANSPORTorToolErrorCategory.TIMEOUT.- If you relied on
ToolSpec.max_retries=3default, pass it explicitly.
[0.8.2] - 2026-04-25 — "Bounded caches for long-running runtimes"¶
Two memory leaks for long-running runtimes. v0.8.1 caught the first
class of leak in Channel; v0.8.2 closes the same shape in MessageBus
and a separate leak in ToolGateway's idempotency cache.
Bounded MessageBus history + queue drain¶
TeamOrchestrator owns a single MessageBus instance that is reused
across every run() call. The orchestrator never calls subscribe(),
so published messages accumulated in per-role asyncio.Queues forever
on top of the unbounded history. v0.8.2 bounds the history and drains
the queues at the end of every run. HandoffResult.messages is already
a detached list(...) copy taken before reset() fires, so callers
that retain the result see exactly what they saw before.
MessageBus(history_limit=N)mirrorsChannel(history_limit=N).None(default) keeps unbounded history — matches pre-v0.8.2 behaviour.int >= 0retains at mostNpast messages per session;0disables history retention entirely. Negative values raiseValueError. Implemented as a per-sessioncollections.deque(maxlen=...);history()still returns a plainlistcopy.MessageBus.reset()clears all history and drains every per-role queue via non-blockingget_nowait(). Required for owners that reuse a single bus across independent runs (e.g.TeamOrchestrator).TeamOrchestrator.run()now callsself._bus.reset()infinally— bus state no longer accumulates across runs.TeamOrchestrator(history_limit=N)— keyword-only; forwarded to the ownedMessageBus.
Per-agent delivery queues in arcana.multi_agent.channel are
intentionally not bounded here — they are driven by the consumer's
receive() calls and an agent registered but never drained is a
consumer bug, not a retention bug.
Bounded ToolGateway idempotency cache¶
ToolGateway._idempotency_cache grew unboundedly for the lifetime of
the owning Runtime and was never cleared on teardown. Any long-running
service that reuses a Runtime across run() calls with
idempotency_key (retries, dedupe, streaming pipelines) leaked memory
proportional to unique-key-count × ToolResult.output size — and
ToolResult.output is exactly the place large payloads land: stdout,
HTTP bodies, file contents.
ToolGateway(idempotency_cache_limit=N)— keyword-only, defaults to1024.Nonekeeps unbounded retention (the explicit opt-in for callers that need it).int >= 0caps at that size via LRU eviction; cache hits refresh MRU viamove_to_end.0disables dedup entirely. Negative values raiseValueError. Backed bycollections.OrderedDict.ToolGateway.close()now clears_idempotency_cacheafterbackend.cleanup(), releasing all retainedToolResultreferences onRuntimeteardown.
The 1024 default is the behaviour change: a caller with more than
1024 live keys will see LRU eviction where previously it saw unbounded
retention. Pass idempotency_cache_limit=None to restore the old
behaviour — but note that was always a leak in long-running processes.
[0.8.1] - 2026-04-22 — "Trace You Can Actually Debug With"¶
Principle 5 (auditability) has always been Arcana's load-bearing promise. v0.8.1 turns the trace from a dump of events into a first-class debugging surface: one command per question, causal links between events, and a single dev-mode switch that makes every turn fully replayable offline.
Also includes a previously-staged memory-leak fix for long-running pools (bounded channel history).
Added — Trace debugging¶
arcana trace explain <run_id> --turn N— single-turn full story. One screen that joins what went in (curated messages, prompt snapshot, context decision, pinned items) with what the LLM said (thinking, assistant text, tool calls) and what the runtime did with it (tool results,TurnAssessment, error events). This is the "why did this turn do that?" command.--jsonfor machine-readable output. Degrades gracefully when prompt snapshots are disabled.arcana trace flow <run_id>— ASCII DAG of the run.Turn 1 → [tool_a ✓, tool_b ✗] → Turn 2 (completed). FollowsTraceEvent.parent_step_idlinks to stitch the causal chain. Compact enough to eyeball in most terminals;--jsonfor tooling.arcana trace show --errors --explain— the error triage shortcut. Lists error events as before, then auto-unfoldstrace explainfor the turn each error belongs to. Deduplicates turns that fired multiple errors.RuntimeConfig.dev_mode: bool = False— single switch that impliestrace_include_prompt_snapshots=True. The idea:dev_mode=Truein development gives you everythingexplainneeds offline, without forcing ops-facing users to opt into PII-bearing snapshots. Explicit per-flag overrides still take precedence when already True.
Added — Trace schema (backward compatible)¶
TraceEvent.parent_step_id: str | None = None— causal link. For a single LLM turn,CONTEXT_DECISION/PROMPT_SNAPSHOT/COGNITIVE_PRIMITIVE/TOOL_CALLevents all share the turn'sstep_idas theirparent_step_id; theTURNevent'sparent_step_idpoints back to the previous turn (the spine). Legacy trace files (written before this release) parse unchanged — the field is optional and defaults toNone.ToolCall.parent_step_id: str | None— threads the turn'sstep_idthrough to theToolGatewaysoTOOL_CALLevents can record it.TraceReader.collect_turn(run_id, turn)— bundles every event attached to one turn (turn event, context decision, prompt snapshot, tool calls, cognitive primitives, errors) via the parent link. The primitive behindtrace explain; usable directly from Python.
Added — Bounded channel history (memory leak fix)¶
Long-running AgentPools retained every Channel message forever, which
turned the pool into a slow memory leak for daemon-style usage. v0.8.1
adds an opt-in bound.
Channel(history_limit=N)inarcana.multi_agent.channel.None(default) keeps unbounded history — pre-v0.8.1 behaviour.int >= 0retains at mostNpast messages;0disables history retention entirely. Negative values raiseValueError. Implemented as acollections.deque(maxlen=...)—Channel.historystill returns a plainlistcopy, so readers are unaffected.AgentPool(channel_history_limit=N)andruntime.collaborate(channel_history_limit=N)— plumb the new knob through so users can set it at the entry point they actually use.
Scope — what is not bounded:
- Per-agent delivery queues (asyncio.Queue per registered agent) are
driven by the consumer's receive() calls. An agent that is
registered but never receives will still grow its queue; that is a
consumer problem, not a history-retention problem, and stays the
user's responsibility.
- SharedContext is a user-written key-value store; its size is the
user's to bound.
Governance¶
- Constitution v3.0 → v3.1 (2026-04-21) — Amend Principle 8: "can see
what others have said" → "is given the means to see what others have
said"; expand agents' role to include addressing and reading decisions.
Clarifies that the framework's multi-agent obligation is to provide
communication infrastructure, not to guarantee message reception.
Resolves the v0.8.0 constitutional audit's only open finding; v0.8.0's
AgentPoolchannel-plus-shared design is now the canonical implementation of Principle 8, not a compromise against it. No code change. Seespecs/constitution-amendment-2-collaboration-means.md. - v1.0.0 removals tracking —
specs/v1.0.0-removals.mdrecords the policy (physical removal, no compatibility shims by default) and the first scheduled entry:Runtime.team()+TeamSession+TeamModemachinery. Rationale is tied to the amended Principle 8 —team(mode= "shared")'s rounds counter and fixed turn order are the exact topology the amendment rules out, so a shim would keep the violation alive.
[0.8.0] - 2026-04-19 — "The Collaborative Cognition Release"¶
Multi-agent pools where each member is an independent cognitive instance.
Extends v0.7.0 primitives to pool settings without adding orchestration —
Principle 8 still holds, there is no graph DSL, no turn scheduler, no role
hierarchy. See the user guide at docs/guide/multi-agent.md and the spec
at specs/v0.8.0-collaborative-cognition.md.
Added — Multi-agent infrastructure¶
runtime.collaborate(budget?, cognitive_primitives?)— returns anAgentPool. Sync factory; the pool itself is an async context manager (async with runtime.collaborate() as pool). Noawaiton the factory.AgentPool.add(name, *, system?, tools?, provider?, model?, max_history?, cognitive_primitives?)— registers a namedChatSessionthat shares the pool'sBudgetTracker,Channel, andSharedContextbut keeps its own prompt, tools, history, and cognitive state.AgentPool.channel— name-addressedChannelwith point-to-point and broadcast delivery.AgentPool.shared— thread-safeSharedContextkey-value store.AgentPool.agents— read-only snapshot of registered sessions.AgentPoolis an async context manager —__aexit__releases sessions, drains the channel, and clears shared state. No orchestration actions (no auto-summaries, no strategy decisions).
Added — Per-agent cognitive primitives¶
- Per-agent
cognitive_primitivesoverride on bothruntime.collaborate(...)(pool default) andpool.add(...)(per-agent override). Resolution: per-agent explicit → pool default →RuntimeConfig.cognitive_primitives. Explicit[]opts an agent out even when a higher level opts in. - Isolated state per pool member — each agent owns its own
CognitiveHandler,PinState, and recall log. Pins made by agent A are never visible to agent B.pin_budget_fractionis evaluated against each agent's own context window. - Tool-name / primitive collision raises
ValueErroratpool.add(...)time — a user tool namedrecall,pin, orunpinthat collides with an active cognitive primitive is rejected instead of silently shadowed (Principle 5).
Added — Pool-aware trace + CLI¶
metadata["source_agent"]— everyTraceEventemitted during a pool run carries the originating agent's name in the existingmetadatadict. TheTraceEventschema itself is unchanged, so v0.6.0/v0.7.0 trace consumers keep working.arcana trace pool-replay <run_id>— summary table listing each pool agent, their event count, and replayable turn list.arcana trace pool-replay <run_id> --agent <name> --turn <N>— per-agent prompt-composition replay scoped to one pool member.arcana trace show <run_id> --agent <name>andarcana trace replay <run_id> --agent <name> --turn N— agent scoping on the existing subcommands.arcana trace showannotates each event line with its[source_agent]tag when present; makes interleaved pool traces readable at a glance.
Added — Contracts¶
- New
arcana.contracts.multi_agent.ChannelMessage— immutable (model_config = ConfigDict(frozen=True)) so the single instanceChannel.sendfans out to all recipients cannot be mutated in place by one receiver at the others' expense. Usemodel_copy(update=...)to derive a modified message. MessageType.CHAT— added for defaultChannelMessageclassification.
Changed — Deprecations¶
runtime.team()is deprecated (emitsDeprecationWarning). Useruntime.collaborate()instead. See the migration recipe at the bottom ofdocs/guide/multi-agent.md. Scheduled for removal in v1.0.0.
Fixed — Pre-release bug fixes (from uncommitted v0.7.x pool work)¶
- Bug:
Runtime.collaborate()wasasync def— the documentedasync with runtime.collaborate() as poolpattern failed withTypeError: 'coroutine' object does not support the asynchronous context manager protocol. Now a sync factory returning anAgentPoolwhose own__aenter__/__aexit__handle the context manager protocol (matchesruntime.chat()). - Bug:
Channel.sendbroadcast shared one mutableChannelMessageacross all recipients plushistory, so a mutation by any receiver bled across the others.ChannelMessageis now frozen; the shared instance is safe to fan out. Regression tests cover both bugs.
Constitutional guard (explicitly NOT done)¶
- No graph DSL, no
StateGraph-equivalent for multi-agent flows. - No turn scheduler. Who talks when is user code (
async for/if/await). - No role hierarchy. Roles live in system prompts, not framework types.
- No auto stop conditions. Stop when user code decides to stop.
- No cross-agent cognitive inheritance. Pool agent A's pins never
populate agent B's state; explicit
pool.shared.set(...)remains the only intentional hand-off.
[0.7.0] - 2026-04-18 — "The Cognitive Primitives Release"¶
Runtime services for the LLM's own reasoning state. The LLM can now invoke
two intercepted tools — recall and pin (with companion unpin) — to
work around the lossiness of working-set compression. See the user guide at
docs/guide/cognitive-primitives.md and Principle 9 in CONSTITUTION.md.
Added — Cognitive primitives¶
recall(turn, include?)— retrieve an earlier turn's messages at full fidelity, bypassing any working-set compression. Supportsinclude="all"(default) /"assistant_only"/"tool_calls"filters. Delegates to the live conversation log, falls back to the trace reader. Out-of-range / invalid turns return structuredRecallResultwithfound=Falseand an actionablenote— never exceptions (Principle 5).pin(content, label?, until_turn?)— protect specific content from compression in future working sets. Returns apin_idthe LLM uses withunpin. Idempotent by SHA-256 of content (duplicate pin returns the existing id). Budget-capped atRuntimeConfig.pin_budget_fraction * total_window(default 50%) — over-cap requests are rejected with a structured diagnosis that includes current usage, requested size, cap, and a remediation suggestion. The framework never auto-unpins or truncates existing pins.unpin(pin_id)— remove an earlier pin; always returns a structured result whether or not the id existed.- Pinned blocks render inside the Working layer as independent
ContextBlock(pinned=True)entries, excluded from_compress_with_relevance/_aggressive_truncate, and surfaced inContextDecision.decisionswithoutcome="kept"andreason="pinned". Principle 2's four-layer structure (Identity/Task/Working/External) is unchanged — no new layer. RuntimeConfig.cognitive_primitives: list[str] = []andRuntimeConfig.pin_budget_fraction: float = 0.5— opt-in per runtime; empty default means no behavioural change.EventType.COGNITIVE_PRIMITIVE— every primitive invocation emits a trace event with{primitive, args, result}metadata.
Added — Contracts¶
- New module
arcana.contracts.cognitive—RecallRequest/Result,PinRequest/Result,UnpinRequest/Result,PinEntry,PinState. ContextBlock.pinned: bool = False— per-block flag.
Added — Runtime¶
arcana.runtime.cognitive.CognitiveHandler— session-local handler that ownsPinStateand services interception, wired intoConversationAgent._execute_toolsvia the same mechanism asask_user.WorkingSetBuilder.set_pin_state(pin_state)— attaches the session's pin state so active pins are rendered as independent messages in every working set build.
Added — CLI¶
arcana trace show <run_id> --cognitive— filter toCOGNITIVE_PRIMITIVEevents with human-readable formatting per primitive.arcana trace show <run_id> --context— pinned entries are now flagged with a[PIN]prefix in the per-message decisions view.arcana trace replay <run_id> --turn N— appends an Active pins at turn N section reconstructed from the run's cognitive events.
Added — Documentation¶
- New user guide:
docs/guide/cognitive-primitives.md. CONSTITUTION.mdv3.0 — Principle 9 (Cognitive Primitives as Services) and two Chapter IV entries.
Constitutional guard (explicitly NOT done)¶
- Framework does not call a primitive on the LLM's behalf; every
invocation is an explicit LLM tool call with a
tool_call_idand trace record. - No system-prompt hints such as "consider using recall here".
- No auto-unpin, no pin truncation, no eviction on budget pressure — the LLM decides how to free budget when rejected.
- Default-off: empty
cognitive_primitiveslist means zero behavioural change over v0.6.0.
Stats¶
- 1368 tests passing (+31 new):
test_cognitive_recall.py(11) +test_cognitive_pin.py(19) +test_context_decision_evidence.py(+1 pinned-block case).
[0.6.0] - 2026-04-17 — "The Explainability Release"¶
Added — Context Explainability¶
MessageDecisioncontract: Structured per-message evidence for every context composition. Records index / role / outcome (kept / compressed / dropped / summarized) / fidelity level (L0–L3) / relevance score / token counts before/after / reason. One entry per input message.ContextDecision.decisions: Authoritative list ofMessageDecisionreplacing the free-text-only explanation. Covers all 5 strategy paths (passthrough, tail_preserve head/tail/middle, aggressive_truncate, LLM summarize, no-summary-budget).- Stale tool result pruning visibility:
_prune_stale_tool_resultsnow returnspruning_infomapping pruned indices to original token counts. Phase 0 pruning is visible indecisionswithreason="stale_tool_result"(or merged with the downstream strategy reason). CONTEXT_DECISIONtrace event: metadata now carries the fullContextDecision.model_dump()andContextReport.model_dump()— consumers can losslessly reconstruct either.
Added — Prompt Snapshots & Replay¶
PromptSnapshotcontract: Captures the exactLLMRequest(messages, tools, model, response_format, budget snapshot) sent to the provider for a single turn.EventType.PROMPT_SNAPSHOT: Emitted before eachgateway.generate()/gateway.stream()when opted in.RuntimeConfig.trace_include_prompt_snapshots: bool = False: Opt-in flag. Default off to avoid PII/secret leakage and trace bloat.TraceReader.list_turns(run_id): Enumerate turn numbers that have replay evidence.TraceReader.replay_prompt(run_id, turn): ReconstructPromptReplayfor a single turn (prompt snapshot + context decision + context report + budget snapshot).arcana trace replay <run_id> --turn N: CLI subcommand for human-readable or JSON replay output. Supports--prompt-only/--decision-only/--jsonmodes.
Constitutional guard (explicitly NOT done)¶
- Framework does not inject
MessageDecisionor relevance scores into the LLM prompt itself (would violate Prohibition 1 / P4) - No automatic recovery, retry, or tool expansion based on decisions (decisions are retrospective evidence, not an input channel)
- Existing strategy selection / compression thresholds / fidelity logic unchanged (pure transparency layer)
- Prompt snapshots default off (anti context/trace hoarding, anti PII leakage)
Stats¶
- 1337 tests passing (+13 new):
test_context_decision_evidence.py(5) +test_trace_replay.py(8)
[0.5.0] - 2026-04-12 — "The Resilience Release"¶
Note: v0.5.0 was authored in CHANGELOG and shipped as part of the v0.6.0 release cycle but never received its own git tag. The 2026-04-12 date is the commit date of
feat: v0.5.0 resilience improvements(e208799). Entries below describe what landed under the v0.5.0 banner.
Added — Runtime OS Reliability¶
- Phase 0 tool result pruning: zero-cost compression stage before strategy-level compression. Old tool results (outside
tool_result_staleness_turns * 3recent messages) replaced with summary placeholders. Error/failure results never pruned. - Iteration budget sharing:
BudgetTracker.max_iterations/iterations_used;Budget.max_iterationspropagates to shared tracker; team agents share a global iteration cap. - MCP dynamic tool discovery:
MCPConnectionlistens fornotifications/tools/list_changed;MCPToolProviderrefreshes the registry on change.
Constitutional review decisions (from specs/v050-upgrade.md)¶
- Rejected: Parallel tool conflict detection — tool authors should self-protect (over-engineering, framework overreach)
- Rejected: Diagnosis → recovery loop — framework crossing into LLM strategy territory (violates Principle 6 boundary)
- Rejected: ChatSession persistence — application-layer concern, not Runtime OS
[0.4.0] - 2026-04-11¶
Added — Execution Isolation Architecture¶
ExecutionBackendprotocol: Pluggable abstraction for WHERE tools execute. Decouples tool logic from execution environment. Ships withInProcessBackend(default, zero overhead). Framework extension point for subprocess/container/remote backendsExecutionChannelprotocol: Pluggable abstraction for HOW the agent loop communicates with tool execution. Enables future physical separation of Brain (reasoning) and Hands (tool execution). Ships withLocalChannel(wraps ToolGateway, zero overhead)ToolGateway.close(): Lifecycle method that invokesbackend.cleanup(), ensuring non-default backends (socket, container, etc.) release resources properlyRuntime.close()chains toToolGateway.close(): Full resource cleanup cascade from Runtime → ToolGateway → ExecutionBackendConversationAgentchannel routing:_execute_tools()prefersExecutionChannelwhen provided, falls back toToolGatewayotherwise.ask_useralways bypasses the channel
Stats¶
- All 1227 tests passing, 0 failures (+25 new tests)
[0.3.3] - 2026-04-06¶
Fixed — Provider Compatibility¶
- Intent router bypasses structured output: When
response_formatis set, the intent router no longer short-circuits todirect_answer(which didn't pass the format to the LLM). Structured output now always goes through the full ConversationAgent loop - Intent router ignores available tools:
classify()now receivesavailable_toolsfrom the tool registry, so "What is X? Use the calc tool" correctly routes to the agent loop instead of direct_answer - Structured output code fence stripping: Providers that return JSON wrapped in markdown code fences (
```json ... ```) are now auto-stripped before parsing. Fixes GLM and MiniMax structured output - Structured output schema prompt strengthened:
json_objectfallback mode now includes exact field names, a concrete example, and "do not rename or omit" instruction. Fixes Kimi/GLM/MiniMax returning wrong field names - MiniMax auto-degraded to prompt-based tools: MiniMax rejects native
tool_callswith 400;ProviderProfileauto-degrades on first failure, subsequent calls use prompt-based fallback seamlessly
Verified Providers¶
Real API verification for all accessible providers: - DeepSeek: direct answer ✓, tool calling ✓, structured output ✓ - OpenAI: direct answer ✓, tool calling ✓, structured output ✓ - Anthropic: direct answer ✓, structured output ✓ (previously verified) - Kimi (Moonshot): direct answer ✓, tool calling ✓, structured output ✓ ← NEW - GLM (Zhipu): direct answer ✓, tool calling ✓, structured output ✓ ← NEW - MiniMax: direct answer ✓, tool calling ✓ (auto-degraded), structured output ✓ ← NEW - Gemini: blocked by region restriction (API key valid) - Ollama: not tested (requires local deployment)
Stats¶
- All 1202 tests passing, 0 failures (+18 new tests)
[0.3.2] - 2026-04-06¶
Changed — Architecture¶
- ChatSession delegates to ConversationAgent:
ChatSession.send()now runs the fullConversationAgentturn loop internally, gaining all V2 features automatically (ask_user, lazy tools, diagnostics, fidelity compression, thinking assessment, streaming events). Removes ~300 lines of duplicated LLM/tool dispatch logic - Memory injection moved to first user message: Memory context is now injected into the first user message instead of the system prompt, keeping the system prompt stable for provider prompt caching
Added — Context Intelligence¶
- Fidelity-graded compression: Context compression now uses 4 fidelity levels instead of binary keep/compress:
- L0 (score ≥ 0.7): Original message preserved verbatim
- L1 (score ≥ 0.4): Condensed to key content
- L2 (score < 0.4): Single summary line
- L3: Dropped entirely
ContextReport.fidelity_distributiontracks the distribution per turnStreamAccumulator: New utility class (runtime/stream_accumulator.py) for assembling streaming chunks into a completeLLMResponse— single state-management point for text, thinking, tool calls, and usageLazyToolRegistry.tool_token_estimate: Cached token estimate for current working set tools, auto-invalidated on expansion/resetMessage.token_countcaching: Token estimation now uses cached property onMessageinstead of re-computing from content text each call
Fixed¶
- Recall tool budgeting: Fixed budget accounting for recall tool invocations
- Zero-budget page table eviction: Fixed edge case where zero remaining budget caused incorrect eviction behavior in context compression
Removed¶
- Virtual memory subsystem: Added in Context OS commit, removed after evaluation — the fidelity spectrum approach achieves the same goals with less complexity
Stats¶
- All 1184 tests passing, 0 failures
[0.3.1] - 2026-04-05¶
Added — Provider Compatibility¶
ProviderProfile: Unified capability system per provider. Trackstool_calls,json_schema,json_mode,streaming,stream_options. Known providers get pre-configured profiles; custom providers get conservative defaults- Auto-degradation: When a provider returns 400 for tool_calls, the profile is updated automatically — subsequent calls skip native tools and use prompt-based fallback. Only fails once per capability
- Custom provider registration:
providers={"siliconflow": {"api_key": "...", "base_url": "...", "model": "...", "tool_calls": False}}— any OpenAI-compatible API with explicit capability overrides ChatSession.send(message, images=[...]): Multimodal messages in chat sessionsruntime.create_chat_session(): Returns ChatSession directly without requiringasync with, for use across HTTP requestsarcana.RuntimeConfig: Now exported from the top-level package
Stats¶
- All 1173 tests passing, 0 failures
[0.3.0] - 2026-04-04 — "The Context Release"¶
Added — Context Transparency¶
ContextReport: Every LLM call now produces a detailed report of how the context window was composed. Shows token allocation across layers (identity, task, tools, history, memory), compression metrics, and window utilization. Available onRunResult.context_reportandChatResponse.context_reportContextStrategy: Adaptive compression strategy system replaces one-size-fits-all compression. Four tiers:- passthrough (< 50% utilization): No compression, zero overhead
- tail_preserve (50-75%): Compress middle history, keep recent turns verbatim
- llm_summarize (75-90%): Use cheap LLM call for semantic summarization
- aggressive_truncate (> 90%): Keep only system + last 2 turns
- Configurable via
Runtime(context_strategy=ContextStrategy(...))or shorthand"off"/"always_compress" - Structured stream events:
runtime.stream()andChatSession.stream()now emit: TOOL_START— tool name and arguments before executionTOOL_END— tool result and duration after executionTURN_END— token count and cost at end of each turnCONTEXT_REPORT— full context composition report per turnStreamEventTypeexported:arcana.StreamEventTypeformatchstatements on stream events
Stats¶
- Tests: 1142 → 1173 (+31 new tests for context features)
- All 1173 tests passing, 0 failures
[0.2.2] - 2026-04-04¶
Fixed — Core Reliability¶
- asyncio.Lock replaces threading.Lock:
Runtime._totals_lockwas athreading.Lockblocking the event loop in async code. Now usesasyncio.Lockfor proper async concurrency - Tool gateway idempotency race: Fixed TOCTOU race where two concurrent calls with the same idempotency key could both execute. Lock now covers the entire check→execute→cache window
- Budget boundary off-by-one:
BudgetTracker.check_budget()used>=(triggers at exact limit), now uses>(allows using exactly the allocated budget). Same fix applied toBudgetScope - Provider close() isolation:
ModelGatewayRegistry.close()now catches exceptions per-provider — one failing provider no longer blocks cleanup of others - MCP reconnect serialization: Added
asyncio.LocktoMCPConnection._reconnect()preventing concurrent reconnect attempts from corrupting transport state - MCP disconnect_all resilience: Individual server disconnect failures no longer abort the cleanup loop
- Graph checkpointer blocking I/O:
GraphCheckpointer.save()/load()/delete()were fake-async (blocking file I/O). Now usesasyncio.to_thread()+ atomic write (temp file + rename) to prevent corruption on crash - Trace reader token/cost accounting:
TraceReader.summarize()usedmax()instead of+=for tokens/cost, reporting peak values instead of totals - SSE line terminator: MCP Streamable HTTP transport now handles
\r\nand\rper SSE spec, not just\n - Silent hook/callback failures: Bare
except: passin agent hooks andon_parse_errorcallback now logs tologger.debugfor debuggability
Added¶
Runtimeas async context manager:async with Runtime(...) as rt:ensuresclose()is called, preventing HTTP connection leaksBudgetTracker.can_afford(estimated_tokens, estimated_cost): Now checks cost budget in addition to token budget
Removed — Dead Code Cleanup¶
orchestrator/: Entire module (Orchestrator, TaskScheduler, TaskGraph, ExecutorPool) — never used by runtimegateway/router.py: ModelRouter — never importedgateway/capabilities.py: CapabilityRegistry — never queriedstreaming/sse.py: SSE formatter — never calledruntime/replay.py: ReplayEngine — never wired uptool_gateway/adapters/langchain.py: LangChain bridge — never loadedstorage/postgres.py,storage/chroma.py: Production storage backends removed. Arcana provides theStorageBackend/VectorStoreinterfaces; users implement for their infrastructure- Removed
chromadbdev dependency
Stats¶
- Tests: 1234 → 1142 (removed 92 tests for deleted dead code)
- All 1142 tests passing, 0 failures
- mypy strict: 8 errors (all pre-existing)
[0.2.1] - 2026-03-28¶
Fixed — Production High Availability¶
- Provider connection leak:
Runtime.close()now cascades to all provider HTTP clients (AsyncOpenAI, AsyncAnthropic). Previously only closed MCP connections, leaking connection pools in long-running apps - Budget race condition:
Runtime._total_tokens_usedand_total_cost_usdnow protected bythreading.Lock. Concurrentrun()calls no longer corrupt cumulative budget counters - timeout_ms actually wired:
ModelConfig.timeout_msnow passed to provider SDKcreate()calls as per-request timeout. Previously the config existed but was silently ignored (SDK defaulted to 600s) - Cancellation safety:
asyncio.CancelledErrorandKeyboardInterruptinRuntime.run()andConversationAgentnow record partial budget and leave state consistent before re-raising
Added — Developer Experience¶
arcana init: CLI scaffold command generatesmain.py+.env.example+agent.yamlfor 30-second quickstartRuntime.on()/Runtime.off(): Event hook API for runtime lifecycle events (run_start,run_end,error). Supports sync and async callbacks, chainableChatSession(max_history=N): Sliding window on message history to prevent OOM in long conversations. System messages always preserved.runtime.chat(max_history=100)- LangChain adapter test suite: 18 tests covering spec extraction, execution, error handling, protocol compliance
- SECURITY.md: Honest security model documentation — what Arcana secures and what it doesn't
- CI coverage reporting: pytest-cov + Codecov upload in GitHub Actions
- Dynamic README badges: PyPI version, CI status, coverage — no more stale static badges
Changed¶
- Example 13 rewritten to use
runtime.chat()/ChatSession.send()instead of manual LLM message management
Stats¶
- Tests: 1164 → 1234 (+70 new tests)
- All 1234 tests passing, 0 failures
[0.2.0] - 2026-03-27¶
Fixed — Structured Output Reliability¶
result.parsedalways returnsBaseModel | None: Fixed bug whereparsedcould be a rawdictwhen provider degrades tojson_objectmode. Now handles dict inputs, validateson_parse_errorcallback returns, and guarantees type consistency- Anthropic structured output:
AnthropicProvidernow supportsresponse_format— injects JSON schema into system prompt (same fallback strategy as DeepSeek/Ollama/Kimi). Works with and without tools
Added — Batch API & Budget Granularity¶
Runtime.run_batch(tasks, concurrency=5): Run multiple independent tasks concurrently withasyncio.Semaphore. Individual failures don't crash the batch. ReturnsBatchResultwith results, succeeded/failed counts, total tokens/cost- Provider-level
batch_generate():OpenAICompatibleProvider.batch_generate(requests, config, concurrency=5)for concurrent LLM calls. Registry-level fallback when provider doesn't implement batch ChainStep.budget: Per-step budget inchain()pipelines. Each step can have its own budget cap, always capped by chain-level remaining budget. Steps without budget share the chain pool
Stats¶
- Tests: 1164, all passing
[0.1.0-beta.8] - 2026-03-27¶
Added — Team Dual Mode¶
runtime.team(mode="shared"|"session"): Two collaboration modes."shared"(default) — all agents share one conversation history."session"— each agent has an independent context; other agents' messages arrive as user messages
Stats¶
- Tests: 1135, all passing
[0.1.0-beta.7] - 2026-03-27¶
Fixed — Provider Compatibility¶
- Cost estimation:
TokenUsage.cost_estimatenow uses realistic mid-range pricing ($0.15/M input, $0.60/M output) instead of placeholder values - Zero-token warning: When a provider reports 0 tokens, the runtime estimates from response length and logs a warning instead of silently tracking $0
- Structured output + json_schema auto-downgrade: Providers that don't support
json_schemaresponse format (DeepSeek, Ollama, Kimi, GLM, MiniMax) automatically fall back tojson_objectwith schema instructions injected into system prompt - Provider model config:
providersdict now accepts{"provider": {"api_key": "...", "model": "..."}}to override default model per provider - Tool call logging: Debug-level logs for all tool calls and results (name, arguments, output)
[0.1.0-beta.6] - 2026-03-26¶
Added — Pipeline & Budget Control¶
- Parallel chain branches:
runtime.chain()now accepts nested lists for parallel execution —[ChainStep, [ChainStep, ChainStep], ChainStep]runs the inner list concurrently withasyncio.gather - Per-run provider/model selection:
runtime.run(provider="openai", model="gpt-4o")overrides default provider/model for a single run. Also available onruntime.stream()andChainStep - Budget scoping:
async with runtime.budget_scope(max_cost_usd=0.50) as scoped:isolates budget for a subset of runs on_parse_errorcallback:runtime.run(response_format=MyModel, on_parse_error=fix_fn)— fires onjson.JSONDecodeErrororpydantic.ValidationError, NOT on provider-level format rejectionresult.parsedfield:RunResult.parsedholds the validated Pydantic model (separate fromresult.outputfor backward compatibility)Toolclass: Non-decorator tool registration —Tool(fn=my_func, when_to_use="...")for when@arcana.toolis not practical
Changed¶
ChainStepnow supportsprovider,model, andon_parse_errorfields- Tools and structured output coexist — agent uses tools during reasoning and returns structured output on the final turn
BudgetScopeexported fromarcanapackage
[0.1.0-beta.5] - 2026-03-25¶
Fixed¶
- 8 user-reported issues: SDK
systemandcontextparameters, fallback chain logging, budget tracking across runs,runtime.fallback_orderproperty, providerget_fallback_chain()method, Tool wrapper support in registry
Added¶
arcana.run(system=..., context=...): System prompt and context injection available at SDK levelruntime.budget_remaining_usd/runtime.tokens_used: Runtime-level cumulative budget tracking properties- Auto fallback chain: Multiple providers automatically form a fallback chain based on registration order
[0.1.0-beta.4] - 2026-03-24¶
Fixed¶
- 14 mypy strict errors regressed after beta.3
ChatSession.send()now usesgenerate()instead ofstream()for reliable usage tracking- CI lint errors (unused imports, import sorting, bare except)
Added¶
- Automated PyPI publish workflow (CI)
- Integration verification tests for b7 features
[0.1.0-beta.3] - 2026-03-24¶
Added — LLM Capability Amplification¶
- Parallel Tool Execution: Multiple tool calls in a single turn now run concurrently via
asyncio.gather, with order-preserving results and individual failure isolation - Prompt Caching: Anthropic system prompt + tool schemas automatically tagged with
cache_control; OpenAIcached_tokenstracked. Up to 90% input token savings on multi-turn runs - Thinking-Informed Assessment:
_assess_turnnow analyzes extended thinking blocks for uncertainty, verification intent, and incomplete information signals. Adjusts confidence and completion accordingly - Structured Output:
arcana.run(response_format=MyModel)returns validated Pydantic instances. Provider-leveljson_schemamode for OpenAI-compatible APIs - Multimodal Input:
arcana.run(images=[...])accepts URLs, file paths, and data URIs. OpenAI ↔ Anthropic content block format auto-conversion - LLM-Driven Context Compression:
WorkingSetBuildercan use a cheap LLM to produce semantic summaries instead of keyword-based truncation. Asyncabuild_conversation_context()with graceful fallback
Added — Interactive Capabilities¶
ask_userBuilt-in Tool: LLM can ask clarifying questions mid-execution. Intercepted at runtime level (bypasses ToolGateway). Sync/asyncinput_handlercallback. Graceful fallback when no handler providedruntime.chat(): Multi-turn conversational sessions with persistent history, shared budget, context compression, and streaming support.ChatSession.send()/ChatSession.stream()- CLI
arcana chat: Interactive terminal chat with Rich formatting, per-turn token/cost stats, budget enforcement - Examples 13-14: Interactive chat and ask_user demonstrations
Changed — Constitution v2¶
- Principle 2 expanded: context is modality-agnostic (text, images, structured data)
- Principle 4 corollary: thinking is signal, not contract — runtime may listen but never constrain
- Principle 8 added: agent autonomy in collaboration — framework provides coordination, never hierarchy
- Chapter IV expanded: User role defined (intent, information, judgment). Two new inviolable rules: user never forced to interact; LLM asks but never blocks
- Contributor Compact: Questions 8-9 added (agent autonomy, user optionality)
Added — Documentation¶
docs/guide/quickstart.md— Installation → Deployment guidedocs/guide/configuration.md— Full configuration reference (16 sections)docs/guide/providers.md— 8 provider setup guides with fallback chainsdocs/guide/api.md— Public API reference (881 lines)
Stats¶
- Tests: 878 → 1045 (+167 new tests)
- All 1045 tests passing, 0 failures
- 9 new features, 4 documentation files
[0.1.0-beta.1] - 2026-03-18¶
Added¶
- Runtime + Session: Long-lived resource container, create once use everywhere
- Runtime.team(): Multi-agent collaboration (constitutional — Runtime provides comm, agents decide strategy)
- Runtime.stream(): Async generator for streaming
- Runtime.graph(): StateGraph factory
- Memory v2: Relevance-based retrieval (keyword + recency + importance + token budget)
- MCP Client: stdio transport, MCPToolProvider → ToolGateway bridge
- CLI:
arcana run/trace/providers/version - ConversationAgent (V2): TurnFacts/TurnAssessment separation, 51% token savings
- 108 new tests: All user-facing modules now covered (713 total)
- Actionable error messages: 8 files improved
- Intent Router: Default on in ConversationAgent
- Diagnostic Recovery: Structured diagnosis in V2
Changed¶
arcana.run()delegates to Runtime, acceptsapi_keyparam- Default engine is V2 ConversationAgent
- Hardcoded model IDs removed — user explicit > provider default > error
- README → "Agent Runtime for Production"
Fixed¶
- AdaptivePolicy execution closure
- Memory injection through direct_answer fast path
- Tool results use native OpenAI format
- single_tool argument generation
[0.1.0-alpha.2] - 2026-03-18¶
Changed¶
arcana.run()now acceptsapi_keyparameter — no .env file needed- Default engine switched to ConversationAgent (V2)
max_stepsrenamed tomax_turnsinarcana.run()engine="conversation"(default) orengine="adaptive"(V1)
Fixed¶
- SDK no longer forces environment variables for API keys
- OpenAI and Anthropic providers now work in
arcana.run() - Clear error message when no API key provided
[0.1.0-alpha.1] - 2026-03-18¶
Added¶
V2 Execution Engine¶
- ConversationAgent: LLM-native execution model with TurnFacts/TurnAssessment separation
- TurnFacts: Raw provider output, zero interpretation
- TurnAssessment: Runtime completion/failure judgment, separate from facts
- 13-step turn contract with 7 invariants
- Streaming via
ConversationAgent.astream()
V2 Architecture¶
- CONSTITUTION.md: 7 design principles, 4 prohibitions
- Intent Router: Rule-based + LLM + Hybrid classifiers, 4 execution paths
- Adaptive Policy: 6 strategy types (direct_answer, single_tool, sequential, parallel, plan_and_execute, pivot)
- Lazy Tool Loading: Keyword-based tool selection, affordance fields on ToolSpec
- Diagnostic Recovery: 7 error categories, structured feedback, RecoveryTracker
- Multi-Model Routing: 5 model roles, complexity-based selection
- Working Set Builder: 4-layer context management (identity/task/working/external)
Provider Infrastructure¶
- BaseProvider Protocol: Replaces ABC, maps to Rust trait
- AnthropicProvider: Native Claude support with extended thinking
- Chinese Providers: Kimi (Moonshot), GLM (Zhipu), MiniMax factory functions
- Capability Registry: 22 capabilities across 8 providers
- Error Hierarchy: RateLimitError, AuthenticationError, ModelNotFoundError, ContentFilterError, ContextLengthError
- StreamChunk: Unified streaming data model
SDK¶
arcana.run(): Zero-config entry point@arcana.tool: Decorator with affordance fields (when_to_use, what_to_expect, failure_meaning)RunResult: Structured result with output, steps, tokens, cost- Budget tracking wired into SDK
Evaluation¶
- EvalMetrics: first_attempt_success, goal_achievement_rate, cost_per_success
- RuleJudge + LLMJudge + HybridJudge
- EvalRunnerV2 with suite reporting
Code Quality¶
- 605 tests, 0 failures
- Verified with real APIs: DeepSeek, OpenAI (gpt-4o-mini), Anthropic (claude-sonnet-4)
- AgentState immutable pattern
- ToolProvider ABC → Protocol
- asyncio.Lock replaces threading.Lock
V1 (Preserved)¶
- Agent + AdaptivePolicy + StepExecutor + Reducer pipeline
- ReAct and PlanExecute policies
- Graph engine (StateGraph, CompiledGraph)
- Multi-agent orchestration (TeamOrchestrator)
- JSONL Trace system
- Budget tracking
- Checkpoint/Resume