Changelog
All notable changes to ARES are documented here. This project follows Semantic Versioning.
0.10.0 - 2026-08-26
Reactive fiber lifecycle, kernel interception points, guarded file writes, and opt-in intelligence controls.
Added
Kernel (ares-cordis)
- Reactive
Pendingfiber state: anActivefiber whose dependency is genuinely withdrawn disposes its effects (LIFO) and restsPending; it reactivates throughLoadingwhen the provider returns. Apply errors stay terminalFailed; peer-version refusals over a live provider still restInactive.Pendingfibers reserve their registry key and surviveprune_disposed EventOptions { prepend, global }on the newon_with/once_withlistener registrations, plusemit_filtered: per-dispatch filtering where non-global listeners are offered to a filter predicate andgloballisteners always join. Existingon/once/emitsignatures are unchangedContext::get_relaxed::<T>(): likeget, but serves a locally-owned value while its provider fiber transitions (Active/Loading/Reloading/Unloading/Pending); disposed andFailedowners stay refused- Fiber state observers:
Fiber::subscribe_statedelivers every lifecycle transition to synchronous observers with panic isolation; the returned handle cancels the subscription - Kernel intercept meta-events: listeners on
internal/get,internal/set,internal/config,internal/update, andinternal/listenerveto or rewrite the matching kernel operation, andinternal/dispatchobserves every non-internal dispatch with its(mode, name, args).internal/getcan replace a strict read's value, refuse the lookup (refuse: true), or redirect it to the parent frame; an erroring chain refuses the read.internal/seterrors veto the provider write with the previous binding left fully intact.internal/config's non-null terminal IS the effective config for that apply pass; an erroring chain rests the fiber terminalFailed(unchanged semantics). Aninternal/updatebail skips the restart and keeps the current application, with the deferred config visible asvetoed_config. Aninternal/listenerbail (or chain error) cancels the registration and returns an inert handle. Every consult short-circuits at map-lookup cost when no listener is registered, a re-entrancy fence keeps reads inside a chain un-intercepted, and the synchronous bridges fall open (warn + allow) on tokio flavors that cannotblock_in_place - Target-carrying dispatch family:
bail_from/waterfall_from/waterfall_async_fromrun the Bail / Waterfall / around-waterfall chains with an optional per-dispatchListenerFilter; a filtered-out listener skips that one dispatch and stays registered - Readiness barriers:
register_with_readinesstakes a composableReadinessBarrier—ReadinessBarrier::new(pred),.and(..)/with_readiness([a, b])AND-composition (empty is vacuously ready),.watching([TypeId])re-kicks the gated fiber when those providers settle through theReflectServicefan-out. While the gate is closed the fiber rests inspectablePending— quiet waiting that never becomesFailed— with the factory run once up front and strictgetkeeping the service out of consumer reach. Complements (does not replace) availability predicates, which still fail loudly toFailed - Cascade batching: concurrent config updates against one provider collapse to a single dependent convergence wave. Providers mid-reapply are marked in an in-flight ledger; dependents defer during the window and converge once per settled batch instead of once per patch
- Name-keyed computed properties:
Context::register_accessor(name, Accessor::{read_only, read_write, setter_only})installs a computed property beside the TypeId service store and returns anEffectHandlewhose disposal removes the declaration and every alias.Context::aliasbinds an alternate name through the same registration. Typed reads surfacePropertyTypeMismatchinstead of a silentNone; writes to a read-only property are refused withReadOnlyProperty; duplicates (including alias collisions) are rejected. Accessor traffic BYPASSES theinternal/get/internal/setintercept waterfalls entirely - Layered intercept chains: intercept layers per TypeId form an ordered outermost..innermost sequence; new registrations APPEND, so the innermost layer stays effective for all existing getters (no caller breakage).
Context::intercept_chainreturns every layer in dispatch order, andContext::chains_structurally_equalcompares two chains by shared-instance identity for restart-decision checks - Lifecycle riders:
Fiber::updatereturnsResult<(), CordisError>— an error on the restart path propagates to the caller and the fiber staysActiveserving its OLD configuration. Aninternal/updateveto parks the deferred config inFiber::vetoed_configand returnsOk. Theinternal/configwaterfall now also covers the activation path, so rewrites apply on first activation, not only on re-applies - Module graph fan-out (opt-in):
cordis::module_graph::ModuleGraphmaps module keys to their dependencies and, given aModuleReloadimplementation,change_manycomputes the TRANSITIVE affected plugin set read-only FIRST, then reloads each affected plugin exactly once per transaction; a failing reload rolls back that plugin while successfully reloaded siblings stayActive. When aModuleGraphis registered on the context, the file watcher's debounced batch fans through it; without one, watcher behavior is unchanged
Logger
- In-kernel
LoggerService: bounded ring (default 1000 records) with monotonic sequences, fan-out to effect-ownedExportersinks (registration returns aDisposablethat removes the sink), and per-name level routing (set_level) with a service-wide fallback (set_default_level, defaultDebug).enabledgates writes before argument assembly.Message::renderapplies printf placeholders%s %d %i %f %o %O %c %C %%(%o/%Orender compact/pretty JSON; unknown specifiers stay literal);%ccolorizes over the ANSI16 palette by an FNV-1a hash of the logger name,%Cadds bold.hyphenate/derived_nameturn type names intokebab-caselogger names (HTTPServer→http-server).LoggerInterceptoverrides thresholds per fiber throughctx.intercept(resolved via the relaxed read); theContextfacade (ctx.info, …) is a no-op when no logger is provided
Timers
cordis::timer: six fiber-scoped primitives —timeout,sleep,interval,interval_stream,debounce,throttle— std-only on a shared wheel thread (min-heap deadlines drained under one short critical section, callbacks outside it, panics caught). Registrations underwith_current_fiberpush labeled undos onto the owning fiber, so dispose or a reactive unload cancels them; dropping a handle does NOT cancel. A disposedIntervalstream yields exactly one finalErr(InactiveEffect)and closes;debouncecollapses bursts to one trailing delivery,throttle
Admin HTTP
- Structured validation errors on the same PATCH endpoint: a config pre-flight can reject with
ValidationIssue { message, path }items aggregated in aValidationError; the 4xx body then carries a machine-readableissuesarray beside the legacyerrorstring (success carries none, and a failed trial leaves no stale slot behind) - Entry moves: PATCH accepts optional
parent/position(EntryPosition) applied move-THEN-update — an invalid placement answers 409 without touching the file or the live tree.POST /admin/cordis/entries/{id}/moverelocates an entry together with its whole{id}:*subtree in one rename cascade. A valid move preserves fiber identity through in-place refresh, so consumers never observe a dispose/recreate window
Tools fence
- Layer 3 write guards on
Fence: writes require a priorfence_readobservation unless the mode allows blind writes (FS_NOT_OBSERVED);CreateIfAbsentrefuses existing paths (FS_EXISTS);ReplaceIfVersioncompares themtime ^ sizefingerprint captured at read time (FS_VERSION_CONFLICT). Bytes land through a sibling temp file renamed into place, new files get0600on unix, errors carry structuredFS_*codes, and a bounded 200-entry audit ring is readable throughaudit_log. Layers 0-2 behave exactly as before
LLM
- Retry-before-salvage JSON policy: the micro engine re-requests a malformed-JSON answer identically up to
json_retriestimes (default 2) before the substring-salvage fallback runs - Per-provider concurrency governor: optional pool setting
max_in_flightcaps simultaneous dispatches per provider, withgovernor_acquire_timeout(default 30 s) bounding the wait. Permits release only at terminal stream items, and saturation fails closed. Without the setting, behavior is unchanged and no wrappers install - Model-profile catalog: one cross-provider
ModelProfiletable (capabilities, context window, speed tier, cost) merging the static tables with runtime catalog entries.lean_hintrenders the whole catalog for prompt injection in well under 50 tokens,describe_fullprints one record, androutepicks the cheapest capable model for a modality. The catalog is opt-in; nothing wires it into default model selection - Guided-output grammar hints:
GenerationHints::guided_grammarcarries a schema-shaped value (JSON object with a"type": "object"root) asresponse_formatjson_schemaon every OpenAI-compatible path; raw GBNF/EBNF-style text rides the provider-specificguided_grammarextension field on non-streaming OpenAI-compatible requests instead. Providers without a channel silently ignore the hint, and an ABSENT hint leaves the wire byte-identical - Micro-call response cache: deterministic-class micro outcomes are served from a bounded least-recently-used map keyed by a content hash over
(model, system template, input)— default 256 entries with a 15-minute TTL and a master switch viaMicroCacheConfig. Hits skip the network entirely, reportlatency_ms: 0, and carry acache_hittelemetry flag. Answers reached through retries or the salvage fallback are NEVER cached
Agent
- Per-subtask cancellation: delegated subtasks register sticky cancel tokens keyed by run/skill id;
SkillEngine::cancel_subtask()flips a token exactly once and is honored at step boundaries alongside the existingEmergencyStophook. An aborted subtask integrates nothing into the parent context - Quote-aware delegation arguments: double-quoted segments parse as single tokens that may contain spaces and
|separators;--parallellatches split-per-token mode (separators ignored),--modelconsumes exactly one token, and--toolsenables the inner tool loop for delegated tasks. Precedence is flags > profile > global
RAG
- Embedding dedup per request: duplicate inputs collapse by whitespace-normalized SHA-256 content hash before the backend call on both the local and HTTP embedding paths; computed vectors fan back to every duplicate slot, so callers receive full-length results while identical texts cost exactly one backend call
Skills
- Delegated-result review gate (opt-in
review_delegated_results): nestedSkillCallresults pass a fixed-template consistency and task-fit review before integration. A rejection replaces the result with a structured rejection that keeps the original for re-dispatch; a reviewer outage passes the result through unchanged. Off by default - Self-check critique rounds (opt-in
SkillEngine::with_self_check_rounds): nestedSkillCallresults pass up to N LLM critique rounds over a cache-stable template before integration; a verbatim reply ends the loop, and an LLM failure keeps the last good answer silently. Off by default - Delegation hygiene: delegated sub-workflows accept only allowlisted step kinds (
delegated_step_not_allowed:), nested tool rounds hard-cap at three (tool_round_cap_exceeded:aborts after exactly three rounds), and slash-command chatter lines are stripped from delegated result text before it enters the parent context
0.9.0, 2026-08-22
Service architecture, unified execution, wrapper removal.
Added
Execute::runwith the full resolve-create-execute pipeline andRunTrackerobservabilityEventsService::waterfall_around: around-middleware waterfall that runscoreat the end; a skip ofnextskips coreContext::injectwaits on theReflectServiceTypeId notifier (ensure_notifier+changed); if ReflectService is absent or the sender is dropped, it falls through to a 5ms poll- Product events:
tools.list/tools.resolve/tools.execute,llm.get_client/llm.complete,llm.generate/llm.generate_tools(ConfigurableAgent),agent.run(waterfall),agent.admit(Dispatch::Bail),agent.started(Dispatch::Parallel) - Skills isolate the request
ctx(isolate::<Tools>(tenant_id)) instead of opening a new root - Skill
LlmCallsteps strictly runLlm::completethrough thellm.completewaterfall;SkillEngineandSkillsServicehave no direct providergenerate_with_historyfallback - Skill
ToolCallsteps runTools::execute(tools.executewaterfall) on the tenant isolate ExecutionResultreturn type with resolution metadata (source tier, run ID)RunTrackertrait extracted toares-agentfor decoupled run observabilityServiceimpl directly onAgentRegistryandConfigBasedLLMFactory(no wrappers needed)agent_config_from_user_agenthelper inares-agent::configurableFiber::refreshreruns registered pluginapplyafter epoch recomputeEventsServiceParallelreturns JSONnull;Serialbails on the first non-null handler result- Store loader factory runs SQL migrations and seeds agent templates
- Overlay fills empty loader
entry.configfromares.toml; TOON reloads notifyToolsandExecute TenantRealmsopen-then-intercept on request paths; dispose on admin tenant delete- JWT research plus remaining v1 stream/agent handlers open the tenant realm before intercept
- Isolate labels win over intercept for the same
TypeId; unlabeled types still intercept - Leftover
execution_stackdualExecuteinstaller removed - Default
ares-serverlibrary build has no axum (httpis optional) and no longer re-exportsProviderRegistry - Single
Executeloader key (ares-agent); Overlay/ServerRuntimeprovide host extras - JWT middleware looks up tenant claims in Store, fail-closes 401 when the tenant does not exist, then opens
TenantRealmsand interceptsTenantContext; user claims isolate with no dummy Free tenant Llm::from_clientis the public test constructor;no_httpno longer buildsProviderRegistry- Root
ares-serverpackage keeps its binary; the library target serves embedders; integration tests depend onares-server/ares-http
Changed
- All 5 execution sites (chat, v1, scheduler, trigger, pipeline) now delegate to
Execute Tools,Llm, andExecutepublic methods run through Cordiswaterfall_aroundwhenEventsServiceis on ctxSkillsServiceandSkillEngineLLM/tool steps use those same events instead of calling the tool or client directlyresolve_agentdelegates to crate-privateResolverwhen available (legacy fallback retained)- Removed
AgentRegistryServiceandLlmFactoryServicewrappers (consumers use types directly) - Deleted deprecated
start_background_reloadfunction and its test - Calculator tool registered via
ctx.plugin(CalculatorService)in addition to legacy path - Version bump to 0.9.0
Tools,Llm,Execute, and skills remain event-first onEventsServicewaterfallsrun_serverstill instantiates Overlay first, then remaining loader entries- Scheduler, pipeline, and trigger domain loops remain native ARES engines behind
Execute ProviderRegistryremains onares-llmforLlm::new/AgentRegistry::from_config- Overlay lives in
crates/ares-http/src/overlay.rs; the server still registers the Overlay factory
0.8.0, 2026-08-21
Service-based architecture with dependency injection.
Added
cordiscrate: typedContextcontainer,Fiberlifecycle,Servicetrait,RegistryServicewith plugin pattern,Loaderwith config reconciliation,EventsServicewith 5 dispatch modes,ReflectServicefor hot-reload coordination.- Unified services:
UnifiedToolService(merges static, runtime, and MCP tools),LlmService(circuit breaker with failover),AgentResolverService(3-tier resolution: tenant, community, system). - Handler migration: 177 handlers moved from
State<AppState>toState<Arc<Context>>withctx.get::<T>(). - Admin API split from single 190KB file into 15 domain-specific modules.
- V1 API split from 73KB file into 5 modules.
- File-watch hot-reload with 500ms debounce (replaces 60s polling).
- Rhai scripting support for custom tools and services.
Changed
AppStategod-struct (17-22 fields) replaced bypub type AppState = Arc<Context>.build_router(ctx)is the primary router constructor;base_routerremains as deprecated shim.- Rust toolchain updated to 1.98.
- All docs humanized (removed AI-sounding prose patterns).
docs/src/SUMMARY.mdnow includes Cordis chapters (mapping, remedies, capabilities, baseline, YAGNI, redesign) plus Architecture.- mdBook GH-pages rebuilt for 0.8.0 (
gh-pagesbranchdocs: rebuild gh-pages book for 0.8.0 Cordis).
0.7.3
Previous release line (see git tags). Changes tracked in git history before changelog formalization.
0.6.3
Multi-provider LLM, tenant agents, and enterprise metering.
This release transforms ARES from a single-provider system into a full multi-provider LLM platform with enterprise-grade tenant management.
Added
- Multi-provider LLM routing: support for 4 providers (Groq, Anthropic, NVIDIA DeepSeek, Ollama) and 11 models through a unified API.
- Model tier system:
fast,balanced,powerful,deepseek, andlocaltiers with automatic provider routing. - Tenant agent system: agents stored in the database per tenant. Template-based provisioning with full CRUD via admin API.
- Agent templates: seed templates applied automatically on startup. New tenants receive a default agent set.
- Usage metering:
usage_eventstable,monthly_usage_cache, anddaily_rate_limitsfor tracking tokens, requests, and costs per tenant. - API key authentication:
Authorization: Bearer ares_xxxon/v1/*routes with tenant scoping. - Enterprise agent templates: 4 specialized agent templates (
trade-classifier,trade-risk,trade-monitor,trade-reporter) for the first enterprise deployment. - Tenant-scoped API routes: both JWT-protected (
/api/trading/*) and API-key (/v1/trading/*) endpoints. - Admin provisioning API: atomic tenant creation: schema + agents + API key in a single operation.
Changed
- Chat handler now resolves
tenant_idfrom authentication context instead of hardcoded values. - Provider configuration moved from code to
ares.tomlfor runtime flexibility. - Rate limit enforcement now operates at both the provider and tenant level.
Fixed
- Chat handler tenant_id resolution for multi-tenant requests.
0.6.2
Streaming and SSE support.
Added
- Server-Sent Events streaming:
POST /v1/chat/streamendpoint for real-time token-by-token responses. - Stream handler: unified streaming across all providers with consistent SSE format.
- Context continuation:
context_idparameter for maintaining conversation history across requests.
Changed
- Response format standardized to
{"response", "agent", "context_id"}across all endpoints.
0.6.1
Tool calling and RAG foundations.
Added
- Tool calling framework: define tools per agent. ARES manages the tool-call loop, execution, and response assembly.
- RAG pipeline: retrieval-augmented generation with pluggable document stores.
- Workflow engine: chain multiple agents into multi-step workflows with deterministic execution.
Changed
- Agent configuration schema extended to support tool definitions and RAG settings.
0.5.0
JWT authentication and user management.
Added
- User registration and login:
POST /api/auth/register,POST /api/auth/login. - JWT token lifecycle: 15-minute access tokens, refresh token rotation, logout/invalidation.
- Role-based access: user roles with permission checks on protected routes.
- Admin authentication:
X-Admin-Secretheader for internal administration endpoints.
Changed
- All
/api/*routes now require JWT authentication. - Error responses standardized with
errorandcodefields.
0.4.0
PostgreSQL backend and multi-tenant schema.
Added
- PostgreSQL integration: full migration from in-memory storage to PostgreSQL with
sqlx. - Auto-migration:
sqlx::migrate!()runs on startup. No manual SQL required. - Tenant schema:
tenants,tenant_agents, andapi_keystables with foreign key relationships. - Tenant tiers: Free, Dev, Pro, and Enterprise tiers with configurable limits.
Changed
- All state persistence moved from in-memory structures to PostgreSQL.
- Connection pooling via
sqlx::PgPoolwith configurable pool size.
For the complete commit history, see the ARES repository on GitHub.