Changelog

Copy Markdown

v0.9.0

Reworks the optional :horde distribution backend so cluster membership is correct, dynamic, and scopable. Full write-up in #134.

members: :participation — dynamic, role-scoped membership

config :sagents, :distribution, :horde
config :sagents, :horde, members: :participation

Membership becomes exactly the nodes that run Sagents.Supervisor, discovered via an OTP :pg group and kept current on :nodeup/:nodedown by the new Sagents.Horde.MembershipManager. Gate Sagents.Supervisor to your agent-hosting role(s) and membership follows automatically — no node-name predicate, and dead nodes are pruned for free. Prefer this over :auto whenever the Erlang cluster also contains nodes that should not host agents.

:partition — isolate participation into independent groups

config :sagents, :horde,
  members: :participation,
  partition: System.get_env("FLY_REGION")

An optional per-node :partition (any stable, opaque grouping key) scopes membership further so a node only clusters with same-partition nodes. The motivating case is geographic — set it to a Fly.io FLY_REGION so an agent for an Illinois user is never placed on, or routed through, a node in France — but it works for any per-node grouping. Cross-partition request routing remains an infra/app concern (e.g. Fly fly-replay). See docs/clustering.md.

Also in this release

See #134 for details on each:

  • members: :auto is now real — potential behaviour change. It previously froze to a one-time node snapshot; it now drives Horde's NodeListener for genuine dynamic membership with dead-node pruning. The undocumented static :members forms (list / function/0 / {m, f, a}) are removed; the only values are :auto (default) and :participation.
  • Registration-timeout resilienceAgentsDynamicSupervisor.start_agent_sync/1 retries on Horde's hardcoded-5s :via registration timeout, via new :registration_retries / :registration_retry_backoff options.
  • FileSystem distribution-safety — dropped Process.alive?/1 checks on potentially-remote pids that could raise.

v0.8.0

A large release that reworks the runtime foundations of the library: a new direct point-to-point event transport (replacing Phoenix.PubSub), session/factory lifecycle ownership moved into the library, interrupts that survive a process restart, a richer interrupt model (:halt, configurable ask_user), structured data extraction through the full middleware stack, cross-process caller-context propagation, and new tool-driven stop conditions.

This entry consolidates everything relevant to upgrading from the previous public release, v0.7.x. The v0.8.0 line went through 13 release candidates; several breaking changes were introduced and then superseded within the RC cycle and therefore do not affect anyone moving directly from v0.7.x to v0.8.0. For the complete, blow-by-blow history of every intermediate change, see the archived v0.8.0-rc.13 changelog.

Breaking changes — see the Upgrading section below.

Upgrading from v0.7.x to v0.8.0

The recommended path is to re-run the generators on a clean, committed workspace and merge your customizations back in, then apply a handful of host-code renames.

1. Regenerate scaffolding. Run mix sagents.setup (or the individual mix sagents.gen.* tasks) with the same options you used originally, accept the overwrites, and merge your customizations back with a diff tool. This absorbs the structural changes in one step: the new Session / Factory / FactoryRouter triad (replacing the old monolithic coordinator.ex + factory.ex), the new agent_subscriber_session.ex template, integer todo ids in valid_todo_entry?/1, the denormalized tool_call_id column in the persistence schema/context, and restorable-interrupt support. #97 #79 #116 #127 #96

2. Transport: Sagents.PubSub is removed. Replace any direct Sagents.PubSub.subscribe/1 / broadcast/2 calls with use Sagents.Subscriber plus the generated subscribe/2 helper, or pass :initial_subscribers when starting servers to enroll the caller inside init/1 and avoid the start/subscribe race. Existing handle_info/2 clauses keep matching — event payload shapes ({:agent, _}, {:file_system, _}, {:status_changed, _, _}, {:llm_deltas, _}, etc.) are unchanged. #79

3. SubAgent: subagent_typetask_name. The task and get_task_instructions tools now take task_name. Rename the key in any interrupt-data pattern match (%{type: :subagent_hitl, task_name: type, sub_agent_id: id}) and in any context.resume_info maps you build for sub-agent resume. Persisted v1 state is migrated to v2 automatically by StateSerializer. The available-tasks listing moved into an ## Available Tasks system-prompt section (suppressible via :include_task_list); update any custom prompts referencing the old wording. #78

4. Session API rename. Coordinator.ensure_session_running/1 is now ensure_agent_session_running/1 — update LiveViews, controllers, and tests. Factory helpers that were get_model/0 / get_middleware/0 become build_model/1 / build_middleware/1, branching on a %FactoryConfig{} struct. Per-request data (timezone, tool_context, project records) now flows through request_opts → FactoryRouter.resolve/3 → %FactoryConfig{} → Factory.create_agent/2 rather than being threaded as positional args. #97

5. Debug subscriptions. AgentServer.subscribe_debug/1 / unsubscribe_debug/1 are removed in favor of AgentServer.subscribe(agent_id, :debug) / unsubscribe(agent_id, :debug). The single-arg subscribe(agent_id) form is unchanged. #94

6. FileSystem: replace_file_lines removed. If your config, prompts, or evals reference it, either drop it (replace_file_text covers the same use cases for most agents) or re-add it as a project-local tool — the previous implementation lives in the #110 diff. Configs passing tools: / tool_descriptions: to Sagents.Middleware.FileSystem must remove the "replace_file_lines" entry. #110

7. Todo ids are integers. Host code calling Sagents.Todo.new/1, State.get_todo/2, or State.delete_todo/2 with string ids must switch to integers (Todo.new/1 now validates greater_than: 0). Code that builds todos from incoming maps should migrate to Sagents.Todo.list_from_maps/1, which assigns positional defaults for missing/non-numeric ids and coerces stringified integers. Persisted snapshots with legacy base64 string ids rehydrate to positional ids automatically on load. #116

8. Opt middleware into interrupt restoration (optional). Custom middleware that produce restorable, data-only interrupt_data should implement Sagents.Middleware.restorable_interrupt?/1 returning true for matching shapes. The default of false preserves the old safe demote-on-load behaviour with no code changes. Built-in AskUserQuestion and HumanInTheLoop already opt in; SubAgent deliberately does not. #96

Added

  • Direct-delivery transportSagents.Publisher / Sagents.Subscriber replace Phoenix.PubSub with monitored point-to-point delivery, an :initial_subscribers start option, and a Presence-based recovery loop for crash-restart and Horde migration. #79
  • Session/Factory lifecycle in the librarySagents.Session owns the session-start lifecycle (router consult, factory invocation, state seeding, supervisor wiring, subscribers) and is idempotent on resume. Sagents.Factory / Sagents.FactoryRouter behaviours, Sagents.Routers.Single for one-factory apps, and a typed %FactoryConfig{} for per-request data. #97
  • Restorable interrupts — an agent that shut down (inactivity timeout, deploy, crash) with a pending ask_user question or HITL approval now boots back into :interrupted status with the original interrupt_data intact, rather than silently demoting to an error. New optional Sagents.Middleware.restorable_interrupt?/1 callback, set_interrupted/3 persistence callback, and cheap pre-deserialization interrupted?/1 read. #96
  • :halt terminal interrupt via the new Sagents.Middleware.Haltable — tools can hard-stop a workflow (e.g. a gating validation tool) without giving the LLM a chance to continue. Includes AgentServer.dismiss_interrupt/1 for UIs to acknowledge a halt and a [:sagents, :agent, :halt] telemetry event. #115
  • Sagents.Extract — structured data extraction that flows through the agent's full middleware stack. The submit tool is owned by the agent and selected via the :until_tool / :until_tool_success stop condition; run/3 returns the tool's processed_content when present. #108 #129 #128
  • Sagents.AgentResult — read helpers for pulling tool results, arguments, processed content, or final text out of Agent.execute/3 return values. #107
  • :until_tool and :until_tool_success stop conditions on Sagents.Agent.execute/3 and Sagents.SubAgent — complete a run when a target tool is called (or, for :until_tool_success, returns a non-error result), enabling the validate-and-retry pattern. #128
  • Sagents.Middleware.ProcessContext — propagates caller-process state (OpenTelemetry trace context, Sentry context, request-scoped Logger metadata, tenant scope) across the three process boundaries an agent invocation crosses, via :keys and :propagators configuration shapes. #82
  • Sagents.StreamingSession — host-agnostic streaming helpers (handle_tool_call_identified/2, handle_tool_execution_update/3) returning changes maps the host merges itself, with multi-tool-safe delta semantics. #104
  • TodoList :inline mode — each successful write_todos additionally persists a todo_snapshot synthetic display message into the transcript. #101 #102
  • AskUserQuestion config pinning — optional allow_other / allow_cancel init options force those values for every question instead of leaving them to the LLM. #124
  • SubAgent :initial_messages for seeding per-call messages, and :include_task_list to opt out of the auto-generated task menu. #100 #78
  • Sagents.AgentServer.save_synthetic_message_from/2 — lets middleware persist user-facing transcript entries through the same display-message pipeline LLM messages use. AskUserQuestion records the user's answer this way. #88 #89
  • Sagents.State.runtime virtual field for process-local values that must never be persisted, with merge_runtime/2. #84
  • agent_id on tool execution context (context.agent_id) so tools can publish events without reaching into state. #86
  • Tooling hardening: Credo, Dialyzer, sobelow, and mix_audit wired into mix precommit and CI. #93 #90 #106

Changed

  • BREAKING: Transport, SubAgent tool arguments, session/factory API, debug subscriptions, the FileSystem tool set, and Sagents.Todo ids all changed — see the Upgrading section above. #79 #78 #97 #94 #110 #116
  • The generated persistence templates denormalize the tool-call linking id into a dedicated indexed tool_call_id column, switching the hot tool-execution queries from a JSONB fragment(...) to indexed equality. New generations are clean; existing host apps absorb this by regenerating as described above. #127
  • Sagents.Middleware documents the full interrupt-data catalog (:ask_user_question, :halt, :subagent_hitl, HITL action-request map, :multiple_interrupts) and the "halt wins" policy. #115
  • Upgraded to Elixir 1.20 and bumped the langchain dependency floor to >= 0.8.11. #122 #106

For the per-RC Added / Changed / Fixed detail behind this summary — including bug fixes resolved within the RC cycle — see the archived v0.8.0-rc.13 changelog.


Changelog entries for v0.1.0 through v0.7.0 have been removed to give the v0.8.0 line a clean slate. The full detailed history remains available in git — see the v0.8.0-rc.13 changelog, which retains every entry back to the initial release.