Changelog¶
All notable changes to Flowgentic are recorded here. The format follows Keep a Changelog, and versions follow Semantic Versioning.
Tags v0.1.0 (3e43c13c, 23 Sep 2025) and 0.2.0 (72e972d4, 2 Oct 2025)
predate this changelog, so their changes are not listed here.
0.4.0 - 2026-10-30¶
A repair release. It removes features that 0.3 shipped broken or unsafe, or that did not serve Flowgentic's purpose: running agent decisions as workflow tasks, checking their outputs and tracing them with the tasks. It adds acceptance of agent outputs and OpenTelemetry tracing.
Breaking changes. Each is detailed below. Where 0.3's behaviour can be kept, the entry says how.
import flowgenticno longer configures logging. To print Flowgentic's records, callflowgentic.configure_logging().- The logging utilities (
flowgentic.utils.Logger, its formatters and filter,add_context_to_log) and the settings file (config.yml,FLOWGENTIC_CONFIG,flowgentic.settings) are gone. Configure logging in the application, or callflowgentic.configure_logging(). - Each decorated call runs once: the retry loop,
flowgentic.langGraph.fault_toleranceand the decorator'sretry=are gone, and an unknown keyword raisesTypeError. The nearest to 0.3's default of 3 attempts of at most 30 s each is, on a graph node,add_node(..., retry_policy=RetryPolicy(max_attempts=3), timeout=30), wheretimeoutneeds LangGraph 1.2 or later. LangGraph's defaultretry_onretriesConnectionErrorand an attempt that timed out, but not the otherOSErrors that 0.3 retried: name the transient ones inretry_on. On a model call, the chat model'smax_retriesandtimeouttake its place; on an AsyncFlow task, a wrapper in the application. flowgentic.academyis gone, and Flowgentic no longer depends on academy-py. Call Academy agents from AsyncFlow tasks directly, withacademy-py>=1.0,<2installed, as Academy actions as AsyncFlow tasks shows.- The node report is gone:
flowgentic.utils.telemetry,LangraphIntegration.agent_introspector,generate_execution_artifactsandLangraphUtils.render_graph. Export the run's spans to a trace backend or a JSON-lines file instead, and draw a graph with LangGraph'sapp.get_graph().draw_png(path). flowgentic.langGraph.memory, the integration'smemory_managerandflowgentic.langGraph.mutable_graphare gone, with two attributes ofExecutionWrappersLangraphthat nothing used. Bound an agent's history with LangChain'sSummarizationMiddleware,ContextEditingMiddlewareortrim_messages, and build a newStateGraphto change a graph between runs.
Added¶
- OpenTelemetry spans that follow OpenTelemetry's GenAI semantic conventions, so an agent's decisions, the AsyncFlow tasks they run in and the model calls inside them form one trace:
- Each call of an agent bound with
bind_agentis aninvoke_agent <identity>span, with the AsyncFlow task's uid and backend (flowgentic.task.uid,flowgentic.task.backend). - Every task and service Flowgentic submits runs in its caller's trace context. AsyncFlow runs a task's function in a pool thread or another process, where a model call used to start a trace of its own.
- Model calls, tools and graphs are recorded by OpenTelemetry's GenAI
instrumentation, and tasks by RHAPSODY, through
flow.start_telemetry().
Flowgentic depends on opentelemetry-api>=1.43,<2 only: without the SDK,
its spans are no-ops. The examples extra adds the SDK, the GenAI
instrumentation for LangChain and OpenAI, and rhapsody-py[telemetry]. See
Telemetry.
- The AI-HPC demo's runners take --trace PATH, which writes the run's spans
and events to PATH, one JSON object per line. examples/telemetry/cost.py
computes cost from such a file in three ways: the cost a provider reported,
when a span carries one; usage times a price, for a remote model; and a share
of a deployment's GPU-hours, for a model on the allocation.
- flowgentic.acceptance, which accepts or rejects an agent's output on
explicit criteria, for runs with no human in the loop.
accept(propose, *, checks=(), attempts=1, fallback=None, name=None):
- asks the producer for a proposal, and runs the checks on it in order,
stopping at the first rejection;
- asks again, within attempts, handing the producer every earlier attempt
so that it can build the re-ask;
- returns an Outcome with the value, the disposition (accepted or
fallback) and every attempt, or raises NotAccepted with the attempts
when nothing passed and there is no fallback.
A check or the producer rejects an output with Rejected(reason, check=...)
or a Pydantic ValidationError; any other exception propagates at once, with
no further attempt. Each call is an accept <name> span with one
accept.attempt <name> span per attempt, and each check that ran emits a
gen_ai.evaluation.result event through OpenTelemetry's Logs API. A rejected
value is recorded only when OpenTelemetry's GenAI content capture puts
content on spans. See
Acceptance.
- The agentic AI-HPC demo's planner passes accept: three checks (the batch
size, the bounds, distinct candidates), one re-ask with the reasons, and the
surrogate's batch as the fallback. Its record of each decision holds the
disposition and every rejected proposal, and the runner prints their count.
The version without Flowgentic writes the same policy by hand, and the
substitution tests check that both reach the same decisions on a re-ask and
on a fallback.
Changed¶
- Breaking:
import flowgenticconfigures nothing. It used to set the root logger to DEBUG, add a handler that printed every record to stdout, start a thread and register anatexithook; in test runs, that handler wrote to streams the runner had closed. Theflowgenticlogger now has no handler: without configuration, warnings and errors reach stderr through Python's last-resort handler, and the rest is the application's. To get Flowgentic's records back, callflowgentic.configure_logging(), which prints them in plain text to stderr; see Logging. - Log records follow a level contract: DEBUG for per-call detail, INFO for
lifecycle events, WARNING for degraded results and refused requests, ERROR
for failures. Per-call records that were INFO are DEBUG, such as the
decorator's "Registering function … as AsyncFlow …". No record carries
prompts, model outputs, message lists or graph states, at any level. Records
use lazy
%-style arguments, and CI checks it. - The AI-HPC demo's decision model keeps the model's raw message
(
with_structured_output(..., include_raw=True)), and rejects an answer it cannot use withRejected: one that does not parse, a refusal, or one cut off at the token limit. Its messages no longer carry token counts set to zero, which claimed a measurement nobody made. - The documentation site follows the latest release: it deploys when the
repository's latest GitHub release is published (not a pre-release, nor a
patch to an older line), or by hand for a given release tag, no longer on
every push to
main. It stops describing changes that no release contains. - CI runs three design-pattern examples,
sequential/research_agent,supervisor/toyandsupervisor/product_research, with a scripted chat model in place ofChatLLMProvider, and checks that each completes. They need OpenRouter credentials, so CI had never run them. Live runs stay opt-in:make tests-integration-llmlocally, or the LLM examples workflow, which a maintainer starts by hand on GitHub with the repository secretOPEN_ROUTER_API_KEY. - Dependencies are ranges, not pins:
radical-asyncflow>=0.6,<0.7, where 0.3 required==0.6.0, because before 1.0 an AsyncFlow minor release may break;langchain-openaiandlangchain-ollamaat>=1.0,<2.0, like the other LangChain packages. Theexamplesanddevextras have floors, anddevkeepspymdown-extensionsbelow 12.2: 12.2 breaks mkdocstrings before 1.0.7, and 1.0.7 needs Python 3.11. CI runs the suite on the lowest versions the ranges allow. A weekly workflow runs the suite and the docs build on the newest releases within them, and the suite on the newest AsyncFlow and Academy releases beyond them. Dependabot updatesuv.lockwithin the ranges, never the ranges themselves, and the workflows' actions.
Removed¶
Nobody uses Flowgentic yet, so these go without a deprecation period.
- Breaking: the retry loop around every decorated call, with
flowgentic.langGraph.fault_tolerance(RetryConfig,LangraphToolFaultTolerance) and theasyncflowdecorator'sretry=argument. Every tool, task, service and graph node ran in it, by default with 3 attempts of at most 30 s each, retryingOSErroramong other errors. On graph nodes it re-ran finished scientific work: with a simulation that kept failing, it made 24 attempts where the application's own policy made 8, and it cancelled and re-ran a node whose work took longer than 30 s until the cycle failed (substitution experiment, row 3). Each call now runs once. A model call's retries and deadline belong to the chat model (max_retries,timeout), a graph node's to LangGraph (add_node(..., retry_policy=..., timeout=...)), and a task's to the application until AsyncFlow has per-task retry, as in the AI-HPC demos. See Retries and timeouts. The decorator's keyword arguments are now explicit, so an unknown one, such asretry=, raisesTypeError;tool_descriptionwas the only other. - Breaking:
flowgentic.academy(AcademyIntegration) and its two examples. In 0.3 it could not run an Academy action through AsyncFlow: every call failed withExchangeClientNotFoundError, and the examples failed at import. Repaired and then compared with direct Academy calls in AsyncFlow tasks, it added no capability: the same results, tasks and agent lifecycle, for 6 fewer lines of application code (Academy substitution check). The guide Academy actions as AsyncFlow tasks now shows the direct calls, with a tested example. Placing Academy agents on HPC resources belongs to RADICAL's execution layer, and an agent that drives the workflow to ADR. Flowgentic no longer depends on academy-py; theexamplesextra bringsacademy-py>=1.0,<2for the example. - Breaking: the settings file:
config.yml, looked up at import in the working directory,~/.flowgentic/, the file named byFLOWGENTIC_CONFIGand the source tree, and theflowgentic.settingsmodule. An unrelatedconfig.ymlin the working directory madeimport flowgenticfail withKeyError, and the file held only the log level, now the application's, and the report's paths, which went with the report. - Breaking:
flowgentic.utils.Logger, its colored and JSON formatters, the base64 redaction filter andflowgentic.utils.add_context_to_log. Useflowgentic.configure_logging()or the application's own logging configuration; OpenTelemetry's logging instrumentation adds trace and span ids to every record. - The
pyyamlandpython-json-loggerdependencies. - Breaking: the node report:
flowgentic.utils.telemetry(GraphIntrospector, its extractor, schemas and report generator);LangraphIntegration.agent_introspectorandgenerate_execution_artifacts;LangraphUtilsand itsrender_graph.
The report rebuilt each node's record from the graph's final messages after
the run. So it saw no tasks, retries or rejected proposals, and it summed
token counts that the demo had set to zero. The spans replace it; any
trace backend that reads OTLP shows them, and so does a JSON-lines file. To
draw a graph, call LangGraph's app.get_graph().draw_png(path). The
design-pattern examples' committed agent_execution_results/ files are gone,
and the examples' entry points return the final state.
- Breaking: the memory manager and the mutable graph. Neither runs agent
decisions or model calls as workflow tasks, checks their outputs or traces
them, and LangChain and LangGraph cover both:
- flowgentic.langGraph.memory (MemoryConfig, MemoryManager,
LangraphMemoryManager, MemoryEnabledState), the integration's
memory_manager, and the example sequential/research_agent_memory. Its
trimming ignored the pairing of a tool call with its result, so every
strategy could return a conversation that a chat API rejects: trim_last
could keep a tool result whose call it had dropped, and
importance_based reordered the messages by score. "summarize" made a
blocking model call on the event loop, and five parameters had no effect.
For an agent built with create_agent, SummarizationMiddleware and
ContextEditingMiddleware from langchain.agents.middleware bound the
history and keep each tool call with its result; in a graph of one's own,
langchain_core.messages.trim_messages does.
- flowgentic.langGraph.mutable_graph (MutableGraph) and its example. It
changed no running graph: each edit recompiled a new sequential
StateGraph from a list of node names, and refused edits returned
False. Within a run, LangGraph routes with conditional edges, Send and
Command(goto=...); between runs, build a new StateGraph.
- ExecutionWrappersLangraph.react_agents and service_registry, which
nothing used.
Fixed¶
langgraph1.1.5 or later is required; 0.3 allowed 1.0. langgraph 1.0 to 1.1.4 install with the newestlanggraph-prebuilt1.0.x, which imports names that langgraph 1.1.5 added, so importing Flowgentic's LangGraph integration failed withImportError.- The
supervisor/toyexample hid the error of a failed run: when the graph raised, thefinallyblock that writes the report read a result that was never set, and the run ended inUnboundLocalError. The graph's own error now propagates. - The
sequential/research_agentexample exited 0 when a stage failed: its nodes route failures to the error handler, so a live run in which every model call failed, for want of a key or in an outage, passedtests/integration/test_integration.py, which checks only the exit code. A run that does not complete now raisesRuntimeErrorwith the stages' errors.
0.3.1 - 2026-10-23¶
A patch release: fixes for the defects that 0.3.0's documentation lists.
Fixed¶
- Memory,
trim_middle: with one free place beside the system messages, every message was kept. The strategy now fills every free place, and the most recent messages take the odd place of an odd count. - Memory,
importance_based: after the first trim, each message was scored by another message's importance. The scores now follow the trimmed history, which also corrects the importance weighting inget_relevant_context. - Memory,
summarize: the summary could take the history one message over the limit. It now fits within the limit. With one free place, or when no summary is made, the strategy trims astrim_lastdoes; a failed summary used to leave its place empty, or keep the oldest messages. MutableGraph.update_graphreturnsFalsefor a replacement node that is already active. It used to list the node twice, and the rebuild raised LangGraph'sValueError.generate_execution_artifacts(generate_report=...)was inverted:Trueskipped the report.Truenow writes it andFalseskips it, and the default becomesTrue, so a call without the flag still writes the report.- Telemetry: tool calls are read from
AIMessage.tool_calls, and malformed ones frominvalid_tool_calls. Only OpenAI-formatadditional_kwargswere read, so other models' tool calls were missing from the report. - Telemetry: an AI message's text is recorded as the node's final response even
when the message has no
additional_kwargs, and list content is joined into text.
0.3.0 - 2026-10-22¶
A consolidation release. It preserves the work since 0.2.0 as a supported,
documented snapshot: fixes and evidence, no new features beyond those already
on main.
Added¶
flowgentic.agent.bind_agent: binds a framework-specific agent component to an AsyncFlow task on a named backend; each call runs the component as that task and returns its structured decision with the agent's identity and timing.- Named AsyncFlow backends: Flowgentic's
asyncflowdecorator takesbackend=and passes it to AsyncFlow, except forEXECUTION_BLOCK. - Service tasks (
AsyncFlowType.SERVICE_TASK) for resident AsyncFlow services, andMutableGraph(flowgentic.langGraph.mutable_graph) for graphs changed at runtime. - AI-HPC coupling demos in
examples/ai-hpc-coupling/: a RADICAL-only baseline, a deterministic LangGraph version, an agentic version and an agent-augmented version, with offline rehearsal decisions and optional ADR control. --integration directfor the agentic demo (direct_agentic_application.py) runs the same agent graph on AsyncFlow without Flowgentic, and the run'scampaign_summary.jsonrecords which integration ran. The substitution experiment compares the two (tests/integration/test_substitution.py,docs/evaluation/substitution.md), including LLM tool selection and an agent handoff driven by a scripted chat model.- An
examplesextra (python-dotenv), included indev. - CI: a strict MkDocs build job, and Python 3.12 in the test matrix.
- Tests that run the offline mutable-graph and service-task examples
(
tests/integration/test_feature_examples.py). - This changelog.
Changed¶
- Breaking: the decorator API moved from
integration.agents.asyncflow(flow_type=TOOL | NODE | UTISL_TASK | BLOCK)tointegration.execution_wrappers.asyncflow(flow_type=AGENT_TOOL_AS_FUNCTION | FUNCTION_TASK | SERVICE_TASK | EXECUTION_BLOCK). Execution reports moved fromintegration.agent_loggertointegration.agent_introspector(flowgentic.utils.telemetry). - Python 3.10 or later is required (0.2.0 allowed 3.9). CI tests 3.10, 3.11 and 3.12.
- Dependencies are released versions:
radical-asyncflow==0.6.0andacademy-py==0.3.0come from PyPI instead of git URLs.- LangGraph, langchain-core and langchain move to 1.x
(
>=1.0,<2.0); the lock tests LangGraph 1.2.16 and langchain-core 1.6.13. python-json-logger>=3.1.0.pyyamlis declared.python-dotenvmoved from the core dependencies to theexamplesextra.- LangGraph 1.x's
ToolNodere-raises tool errors by default, where 0.6 returned them to the model as messages. A Flowgentic tool that still fails after its retries now aborts the graph run; passhandle_tool_errors=TruetoToolNodeto keep the 0.6 behaviour. - The LangGraph examples build their agents with
langchain.agents.create_agent, which replaces LangGraph's deprecatedcreate_react_agent. - The AI-HPC demo launchers use the installed AsyncFlow, and import a local
checkout only when
ASYNCFLOW_SRCis set.make examples-ai-hpc-deterministicandmake examples-ai-hpc-agenticcreate the environment and run in a fresh clone. - ADR mode (
--controller adr) is documented as unsupported. It needs an ADR source checkout (ADR_SRC). - Development uses
uvand the checked-inuv.lock. CI checks formatting with the locked Ruff, and builds the documentation strictly from the locked environment. - The documentation site describes 0.3.0: its status, a getting-started page,
guides for
bind_agent, forLangraphIntegrationand for LangGraph on AsyncFlow without Flowgentic, a project status page and this changelog. Its code blocks include tested files, and the strict build also fails on broken links and anchors. The README matches the site's home page.
Removed¶
- The
langchain-communityandlangchain-mcp-adaptersdependencies, which nothing imported. - The
flowgenticconsole script, whose targetflowgentic:maindid not exist. - The committed results folder
examples/ai-hpc-coupling/agentic_demo_results/. - Two unused
create_react_agentimports fromflowgentic.langGraph. - Documentation pages for code that is not on
main: MCP tools (AGENT_TOOL_AS_MCP, archived asarchive/feature/mcp), the framework × backend roadmap, the hierarchical design pattern, which has no example, and the chatbot pattern, whose interactive example no test runs.
Fixed¶
- Installing the wheel with pip. It could resolve
python-json-logger2.x, which lacks thepythonjsonlogger.jsonmodule Flowgentic imports, andpyyamlwas imported but not declared. - The agentic demos' candidate policy no longer rejects a proposal at a bound for rounding alone, and never returns a candidate outside its bounds.
create_task_description_handoff_tool: every handoff failed withAttributeError, because the tool called aGraphIntrospectormethod removed in October 2025. The call is gone, and handoffs work, with Pydantic and TypedDict graph states (such ascreate_agent's).- The LangGraph examples import again: they used
ConcurrentExecutionBackend, which AsyncFlow 0.3.0 and later do not export, and now useLocalExecutionBackend. - Seven
mkdocs build --strictwarnings: three missing annotations and four broken links. - The mutable-graph example's conditional graph always took the standard route: its routing flag was missing from the state schema that the graph and the router read.
- The supervisor toy example stored agent B's result as
agent_b, and its gather node, which readsagent_B, passed "N/A" to the synthesis model. - CI:
astral-sh/setup-uvis pinned to an existing release (it publishes nov9tag).- Each test matrix job runs its own Python with the dev extra.
- The bandit job runs again.