Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
Added
VectorStore#size— document count for all backends, contract coverage for RedisSearch and Pgvector (#240):VectorStore::Basegains#sizeas an abstract method;InMemory,RedisSearch, andPgvectorall implement it.RedisSearch#sizequeriesFT.INFO num_docs;Pgvector#sizedelegates tomodel_class.count. Thea_vector_storeshared example is applied to RedisSearch and Pgvector (nightly real-backend CI); unit specs add a skip-guardedit_behaves_likereference and dedicated#sizeunit tests.empty_storeoverride hook added to the shared example for real-backend callers.force_kill: falsedefault indispatch_parallel,fan_out, andEventLoop#stop(#235): Thread#kill is now opt-in. The defaultforce_kill: falseleaves timed-out workers running and raisesTimeoutErrorimmediately, avoiding the risk of interruptedensureblocks or corrupted database transactions. Passforce_kill: trueto restore the previous behaviour (with alogger.warnto make it visible).EventLoop#stopgains the same keyword and returns:timeoutinstead of:force_killedwhenforce_kill: falseand the thread is still alive.Public API compatibility snapshot spec (#236):
spec/phronomy/public_api_spec.rbenumerates expected public methods for everyStable-tagged constant. The spec runs as part of the default RSpec suite; any accidental removal or rename of a listed method now fails CI immediately.Nightly real-backend CI split into three independent job groups (#238): The nightly workflow (
nightly.yml) now has three separately skippable jobs:real-backend-redis(Redis Stack),real-backend-pgvector(PostgreSQL + pgvector), andreal-backend-otel(OpenTelemetry in-process SDK exporter). Each job runs only the relevant spec with--tag real_backend:<backend>. The existingredis_search_specandpgvector_specgain thereal_backend:metadata tag. A newotel_spec.rbverifies span emission, attribute attachment, and error recording viaInMemorySpanExporter.CancellationToken#raise_if_cancelled!— convenience cancellation check (#234): New instance method that raisesPhronomy::CancellationErrorwhen the token is cancelled, or returnsnilotherwise. Replaces theif cancelled? then raisepattern inside tools, RAG loaders, and hooks.Tool cooperative cancellation via
cancellation_token:keyword (#234):Tool::Base#callnow injectsThread.current[:phronomy_cancellation_token]ascancellation_token:intoexecutewhen the method declares that keyword. Existing tools without the keyword continue to work unchanged. Tool authors can opt in:def execute(query:, cancellation_token: nil).CancellationToken.timeout_after— monotonic-clock deadline (#225): NewCancellationToken.timeout_after(seconds)class method creates a token that becomes cancelled after the specified number of seconds, measured withProcess::CLOCK_MONOTONIC(immune to NTP/DST drift). The existingdeadline:keyword for wall-clock deadlines remains supported for backward compatibility.EventLoop#stop— drain mode and cooperative shutdown (#233):EventLoop#stopnow accepts adrain: truekeyword (default:false). When set, the loop waits up toPhronomy.configuration.event_loop_stop_grace_seconds(default: 5 s, configurable) for in-flight FSM sessions to complete before joining threads. New sessions submitted while shutdown is pending are rejected immediately withPhronomy::CancellationError. A newevent_loop_stop_grace_secondsconfiguration attribute is available onPhronomy::Configuration.invoke_timeoutDSL andPhronomy::TimeoutError: Agents can declare a per-invoke timeout in seconds viainvoke_timeout Nin the class body. Exceeding the timeout raisesPhronomy::TimeoutError(a subclass ofPhronomy::Error). The default remains unlimited.dispatch_parallel/fan_out— per-calltimeout:option (#133): Both methods now accepttimeout: nil(default, unlimited) or a positiveNumericin seconds. Timed-out tasks are treated the same as errors and follow the existingon_error:policy (:raiseor:skip).MCP
HttpTransportcustom authentication headers (#144):McpTool.from_servernow acceptsheaders: {}, forwarded all the way toHttpTransport#initialize. Arbitrary headers (e.g.Authorization: Bearer …) are injected into every JSON-RPC request, enabling use of MCP servers that require bearer tokens or API keys.StdioTransport—env:,cwd:, andstartup_timeout:options (#145): Three new keyword arguments are now accepted when constructing aStdioTransport(and therefore viaMcpTool.from_server):env: {}merges extra variables into the child process environment;cwd: nilsets the working directory;startup_timeout: 5limits how long to wait for the child process to become ready.Workflow DSL validates graph structure at build time (#124):
Phronomy::Workflow.definenow raisesArgumentErrorimmediately for hard structural errors (no states declared, transitions referencing undefined targets). Unreachable states emit a warning but do not raise. Errors surface at load time rather than at the firstinvoke.Expanded error taxonomy (#149): Five new subclasses of
Phronomy::Errorare now available:TransportError(MCP or LLM network-layer failure; subclasses areRateLimitErrorfor HTTP 429 andAuthenticationErrorfor HTTP 401/403),ContextLengthError(prompt exceeds model context window), andCancellationError(explicit invocation cancellation, distinct from the deadline-exceededTimeoutError). All five are defined as subclasses ofPhronomy::Errorso application code can rescue them uniformly.Agent::Base.static_knowledge_refresh!(#164): New class-level method that clears the cachedstatic_knowledgechunks so the nextinvokere-fetches from all registered sources. Essential for long-running processes (web servers, job workers) where knowledge sources may be updated at runtime without a process restart.Phronomy::Configuration#logger(#158): New optional configuration attribute. Any object responding to#warn(e.g.Rails.logger) can be assigned. Framework diagnostic messages — starting with the unreachable-state warning fromWorkflow.define— are routed through this logger instead of writing directly to$stderrviaKernel#warn.Phronomy.with_configurationandPhronomy.reset_runtime!(#206): Two new class methods for runtime isolation.with_configurationyields the currentConfigurationobject and restores the original after the block — even on exception — enabling per-request overrides and scoped test configuration.reset_runtime!stops any runningEventLoop, clears its singleton, and resets configuration to defaults; intended for test suites to ensure clean state between examples.spec_helper.rbnow callsreset_runtime!in anafter(:each)hook automatically.CancellationToken— cooperative cancellation for agent invocations (#216): New classPhronomy::CancellationTokenenables cooperative cancellation withoutThread#kill. Tokens are passed viaconfig: { cancellation_token: token }.cancel!marks the token (thread-safe via Mutex);cancelled?returnstrueonce cancelled or once an optionaldeadline: Timehas passed. Agents check the token in_invoke_impl(fail-fast before any LLM call) and again immediately beforechat.ask.CancellationErroris never retried by the retry policy.dispatch_parallelandfan_outacceptcancellation_token:and automatically inject it into every worker task's config unless the task already supplies its own.
Changed
CancellationTokenchecked at granular checkpoints (#223): The cancellation token (passed viaconfig: { cancellation_token: token }) is now checked at multiple additional points beyond the initial LLM call boundary: before eachKnowledgeSource#fetchinbuild_context(RAG phase); after each streaming chunk in_stream_impl; before each tool-call batch inParallelToolChat; and after eachbefore_completionhook. This ensures that long-running retrieval, streaming, and tool-dispatch phases respect cancellation with minimal latency.Agent::OrchestratorusesCancellationTokenfor internal stop flag (#224): The boolean stop flag inOrchestratoris replaced with an internalCancellationToken. FSM session loops perform cooperative cancellation checks viacancelled?;Thread#killis retained only as a last resort after cooperative shutdown.Error taxonomy classes are now raised at the retry boundary (#204): The classes
Phronomy::RateLimitError,Phronomy::AuthenticationError,Phronomy::ContextLengthError, andPhronomy::TransportError(introduced in #149) are now actually raised when the correspondingRubyLLMexceptions occur. A new internalErrorTranslationconcern wraps the retry exhaust path and mapsRubyLLM::*exceptions to their Phronomy counterparts, preserving the original exception as#cause. Migration: callers rescuingRubyLLM::RateLimitError(or otherRubyLLM::*errors) directly should migrate torescue Phronomy::RateLimitError/Phronomy::TransportErroretc.Orchestrator#bounded_mapuses cooperative cancellation before force-kill (#203): Workers now check a sharedcancelledflag at each loop iteration and stop picking up new tasks once the timeout deadline passes. A 0.5 s grace period is given to in-flight workers beforeThread#killis used as a last resort.EventLoop#stopsimilarly logs a warning viaPhronomy.configuration.loggerwhen force-kill is triggered.Orchestrator#bounded_maptimeout deadline uses monotonic clock (#209): ReplacedTime.nowdeadline arithmetic withProcess.clock_gettime(Process::CLOCK_MONOTONIC)to avoid sensitivity to NTP adjustments, DST transitions, and system-clock changes that could inflate or deflate effective timeouts.EventLoopwarns on events for unknowntarget_id: When the event loop receives an event whosetarget_iddoes not match any registered session, a warning is emitted instead of silently discarding the event.VectorStore#searchvalidateskis a positive integer: All three backends (InMemory,RedisSearch,Pgvector) now raiseArgumentErrorimmediately whenkis not a positive integer, providing a clear error instead of a silent empty result or an obscure database error.max_parallel_toolsDSL: Agents can cap the number of concurrent tool-call threads withmax_parallel_tools Nin the class body. Useful for rate-limiting external API calls. The default is 10 (inheriting fromBase); set explicitly to raise or lower the cap.max_parallel_toolsandinvoke_timeoutDSL argument validation (#152): Both setters now raiseArgumentErrorat class-definition time if the supplied value is invalid (max_parallel_toolsrequires anInteger >= 1;invoke_timeoutrequires a positiveNumeric), surfacing configuration mistakes immediately.on_error :suppress— canonical alias for:return_empty(#165)::suppressis the new preferred name for the error-suppression behaviour inTool::Base.:return_emptycontinues to function but emits a deprecation warning and will be removed in a future major release. Migrate by replacingon_error :return_emptywithon_error :suppress.Tool nested object properties injected into JSON Schema (#162):
Tool::Base#params_schemanow recursively serialises nested:objectparam specs (includingenumconstraints and further nesting) into the JSON Schemapropertiesstructure forwarded to the LLM, enabling accurate structured argument generation for complex tool parameters.
Fixed
EventLoop#startis now idempotent; stale:__stop__sentinel race fixed (#203): Callingstarton an already-runningEventLoopis now a no-op. Fixed a race condition wherestopsetting@running = falsebefore the worker thread was scheduled left the:__stop__sentinel unconsumed in the queue; a subsequentstartwould then immediately terminate the new thread upon popping the stale sentinel. The sentinel is now treated as a pure unblock signal forqueue.pop(nextinstead ofbreak) — loop termination is driven solely by@running.trace_pii: falsenow redacts both input and output: Previously only the user input was redacted whentrace_piiwasfalse; LLM responses and tool results were still forwarded to the tracing backend unredacted. Both sides are now replaced with[REDACTED].StdioTransport—read_timeoutprevents indefinite blocking: A configurableread_timeout(default 30 s) is now enforced on MCP stdio reads. A silent child process could previously block the calling thread forever.MCP schema
requiredandenumconstraints propagated toparamDSL:McpTool.from_servernow copiesrequiredandenumconstraints from the MCP JSON Schema into the generatedparamdeclarations so downstream validation sees them.FSMSessionnotifies parent when childAgentFSMfails: An unhandled error in a childAgentFSMnow correctly notifies the parentFSMSession, preventing it from waiting indefinitely for a completion event that will never arrive.WorkflowContext.fieldrejects plainArrayorHashdefaults: Passing a plainArrayorHashas a field default now raisesArgumentErrorat class-definition time, preventing accidental state sharing across workflow invocations. Other mutable objects are not checked. Wrap collection defaults in a Proc:default: -> { [] }.Tool aliases inherited by
Agentsubclasses:tool_aliasesdeclared in a parentAgent::Basesubclass are now correctly merged into subclasses rather than being silently dropped.ReactAgentoutput selection skips tool-role messages: The final output selection logic no longer misidentifiestool-role messages as the assistant response, fixing spurious tool-call JSON appearing inresult[:output].Thread-local context cache cleaned up after each
invoke(#128):Agent::Base#invokepreviously leaked thread-local context cache entries after each call, causing stale cache hits in long-lived threads. The cache is now cleared in anensureblock.Unknown tool parameters are rejected (#130):
Tool::Base#callnow raisesArgumentErrorwhen keyword arguments not declared via theparamDSL are passed, instead of forwarding them silently toexecute.EventLoop#stopuses cooperative shutdown instead ofThread#kill(#135):Thread#killbypassesensureblocks and is unsafe. The event loop now sets a sentinel flag and joins the worker thread, allowing it to flush pending events before termination.Orchestratorpropagates parentconfigandthread_idto sub-agents (#132): Sub-agents spawned viadispatchordispatch_parallelnow inherit the caller'sconfighash andthread_id, enabling correct memory isolation and distributed tracing in multi-agent pipelines.Agent::Basecachesstatic_knowledgefetch at the class level (#127): The RAG knowledge fetch was re-executed on everyinvoke. The result is now memoized at the class level (@static_knowledge_chunks ||= ...), eliminating redundant vector-store queries. The cache is not invalidated automatically when source content changes; callstatic_knowledge_refresh!explicitly to force a reload.WorkflowContext#initializeraises on unknown field keys (#121): Passing an unrecognised key toWorkflowContext.newwas silently ignored. The constructor now raisesArgumentError, surfacing typos and API mismatches immediately.WorkflowContext#mergeraisesArgumentErrorfor unknown field keys (#154): Passing an unrecognised key toWorkflowContext#mergewas silently ignored. The method now raisesArgumentError, matching the guard added to#initializein #121.WorkflowContext#deep_dup_valuerescuesTypeErrorfor non-dupable objects (#156): Objects that raiseTypeErrorfrom#dup(e.g.Method, frozenProc,Integer,Symbol) are now returned as-is instead of crashing.Workflow.defineraises for undefinedfrom:state in transitions (#157): Transitions that reference afrom:state not declared in the DSL now raiseArgumentErrorat build time, complementing the existing check for undefinedto:targets.Workflow.defineunreachable-state warning routes through configured logger (#158): The diagnostic warning for unreachable states now usesPhronomy.configuration.loggerwhen set, falling back toKernel#warn. Previously the warning always went to$stderr.require "set"added toworkflow.rb(#159): Eliminates an implicit dependency onSetbeing pre-loaded by another gem.Tool::Base#validate_nested_objectrejects undeclared extra keys (#166): Keys present in the LLM-supplied hash but absent from the tool's nestedparamschema now produce a validation error rather than being silently forwarded.WorkflowContext#mergedeep-copies unchanged fields (#123): Fields absent from themergeargument were previously shared by reference with the original context, allowing one branch to mutate another branch's state. All fields are now independently copied.Robust metadata parsing in
VectorStore::Pgvector#search(#139): Metadata stored as a PostgreSQL JSON string is now parsed correctly regardless of whether the database driver returns aStringor an already-decodedHash.OutputParser::JsonParsertries all fenced code blocks before falling back (#146): The parser now scans every fenced block in the LLM response (in order) and returns the first one that parses as valid JSON, rather than only checking the first block. This improves reliability with models that include prose before the JSON block.on_error: :return_emptyemits a warning and returns a descriptive string (#147): Errors in tools that declareon_error :return_emptyare now logged towarnbefore the tool returns. The placeholder string includes the tool name and a brief reason, making silent failures easier to diagnose.context_version_cacheaccessible afterinvokecompletes: The thread-local cache is cleared ininvoke'sensureblock, which causedcontext_version_cacheto returnnilimmediately after every call. The value is now persisted in@last_context_version_cacheso it remains readable post-invoke.WorkflowContextfield type:mergecomment corrected: The inline comment incorrectly described:mergeas a deep-merge. It performs a shallow merge (Hash#merge). The comment has been updated.WorkflowContextreturn value from entry actions now adopted in EventLoop mode (#107):FSMSessionpreviously discarded theWorkflowContextreturned by entry action callables, causings.merge(...)updates to be silently lost whenevent_loop = true. The context is now correctly propagated, bringing EventLoop semantics in line with the synchronousWorkflowRunner. Regression tests added inspec/phronomy/fsm_session_spec.rb(unit) andspec/integration/workflow_spec.rb(integration, both sync and EventLoop paths).
Documentation
trace_pii = falsedescription corrected (#153): The inline comment and README Note now correctly state that both the input and the output are redacted.invoke_timeoutis a wait timeout, not cancellation (#163): YARD comment now explicitly documents that the background agent thread and in-flight LLM/tool calls are not interrupted when the timeout fires. Only the caller receivesTimeoutError.context_version_cachethread-safety limitation documented (#161): A NOTE in the YARD comment explains that the per-instance cache is not thread-safe when the same agent instance is shared across threads.trace_piioption documented in README: Thetrace_pii:configuration key and its behaviour (defaultfalse, redacts input and output in trace records) is now described in the Configuration section of the README.CJK token under-count warning in
TokenEstimator: A note in both the source and README explains that the byte-based heuristic under-counts CJK characters by roughly 3×. Users processing Chinese, Japanese, or Korean content should apply a correction factor or use a model-specific tokenizer.Stability labels,
reset_configuration!caveat, CI, and gemspec (#140 / #141 / #142 / #143 / #148 / #150): README stability table revised for several APIs.Phronomy.reset_configuration!now carries a warning that it is intended for test isolation only. Gemspec upper bounds added forruby_llmandpg.ruby headadded to the CI test matrix. README API smoke tests added.
[0.6.0] - 2026-05-21
Removed
Phronomy::Guardrail::Builtinmodule removed:PromptInjectionDetectorandPIIPatternDetectorare opt-in pattern-matching helpers that encode application-level policy decisions (which phrases to block, which PII categories to detect, which languages to support). Shipping them as gem defaults was misleading — their correct home is inside each application that needs them. Reference implementations are now provided in example 06 ofphronomy-examples. ExtendPhronomy::Guardrail::InputGuardraildirectly to create equivalent guardrails in your application.
[0.5.4] - 2026-05-20
New Features
VectorStore embedding dimension validation (#98): All three vector store implementations (
InMemory,RedisSearch,Pgvector) now validate that every embedding passed toaddandsearchmatches the expected dimension. Dimension is inferred automatically from the firstaddcall; alternatively it can be set explicitly viainitialize(dimension: N). A mismatch raisesArgumentErrorwith a descriptive message. Thesearchmethod never establishes the dimension — it only validates when a dimension is already known.clearretains the established dimension (schema property).dispatch_parallel/fan_outconcurrency controls (#99): Two new keyword arguments are now accepted by both methods.max_concurrency: nil(default) or a positiveInteger— caps the number of worker threads.nilmeans one thread per task (previous behaviour).on_error: :raise(default) or:skip— controls failure handling.:raiseruns all tasks to completion then re-raises the first error in input order (fail-last, not fail-fast).:skipfills failed slots withniland never raises. The underlying implementation uses aQueue-based bounded worker pool (bounded_map) for predictable resource usage.
[0.5.3] - 2026-05-20
Bug Fixes
- Ensure
from_servercloses transport on error (#95): The short-lived transport created insideMcpTool.from_serveris now wrapped inbegin/ensure, so the underlying child process (stdio) is always terminated even whenfetch_toolraises. - Correct
McpTool#closedocumentation (#94): The comment previously stated that callingexecuteaftercloseraises an error; in practice the transport reopens automatically. The comment now reflects actual behaviour.
Documentation
- Fix CHANGELOG date for v0.5.1 (#96): The v0.5.1 entry had an incorrect date of 2026-05-21; corrected to 2026-05-20.
[0.5.2] - 2026-05-20
Bug Fixes
- CHANGELOG correction for v0.5.1 MCP fix (#90): The v0.5.1 entry
incorrectly stated that a
Mutexwas added toStdioTransport#rpc_call. The actual fix was per-instance transport ownership (eachMcpToolinstance creates its own transport ininitialize). Corrected the description.
Enhancements
- Add
McpTool#close(#92): Tool instances now expose aclosemethod that shuts down the underlying stdio child process (StdioTransport) or releases the HTTP connection (HttpTransport). This gives callers a deterministic way to clean up resources instead of relying on GC.
Maintenance
Archive stale Rails integration design doc (#91): Added an archived notice to
spec/design/17_rails_integration.mdclarifying that Rails integration was removed in v0.3.0–v0.5.1 and the document is for historical reference only.Remove zombie
register_workflow_contextAPI (#93): ThePhronomy.register_workflow_context,workflow_context_registry, andreset_workflow_context_registry!methods (along with the backing@workflow_context_registryand@registry_mutexmodule-level variables) were removed fromlib/phronomy.rb. These existed to support theStateStoredeserialization guard, which was removed in a prior release. The API had no remaining callers in the codebase and was not listed in the README stability table.
[0.5.1] - 2026-05-20
Bug Fixes
Remove broken Rails generator and Railtie (#85): The generator template referenced
Phronomy::ActiveRecord::ActsAswhich no longer exists, causingrails generate phronomy:installto produce broken model files. Removedlib/generators/,lib/phronomy/railtie.rb, and all references inlib/phronomy.rb.Fix MCP transport ownership (#86):
McpToolno longer stores a shared transport at class level.from_servernow uses a short-lived transport only to fetch tool metadata and callscloseimmediately after. Each tool instance creates its ownStdioTransportorHttpTransportininitialize, so concurrent callers (e.g. viaOrchestrator#dispatch_parallel) never share stdio streams. NoMutexis needed. Also adds missingrequire "securerandom"and a no-opHttpTransport#closefor interface consistency.
Documentation
- README corrections (#87): Remove stale Rails generator installation
instructions. Clarify that
TeamCoordinatorworker state is local to a singleinvokecall (not persistent across calls). Annotate app-level examples (09_rails_chat,15_rails_secure_chat,18_rails_agent_job,19_trust_pipeline) as requiring external infrastructure. Add scope note toAgent::Orchestratorsection.
Maintenance
- Add version guard to
ruby_llm_patches.rb(#88): The monkey-patch for the upstreamhandle_error_chunkbug (ruby_llm <= 1.15.0) is now gated behind aGem::Versioncheck so upgrading ruby_llm will automatically disable the override.
[0.5.0] - 2026-05-20
Breaking Changes
Agent::Base#invokeand#stream—messagesandthread_idpromoted to top-level keyword arguments: Previously these values were passed inside theconfig:hash. They are now explicit keyword arguments. Theconfig:hash retains other runtime options such as:knowledge_sources,:user_id, and:session_id.
Before (v0.4.x):
agent.invoke(input, config: { messages: prior_msgs, thread_id: "t1" })
agent.stream(input, config: { messages: prior_msgs, thread_id: "t1" }) { |e| ... }
After (v0.5.0):
agent.invoke(input, messages: prior_msgs, thread_id: "t1")
agent.stream(input, messages: prior_msgs, thread_id: "t1") { |e| ... }
Applications that only pass :knowledge_sources, :user_id, or :session_id
in config: require no changes.
Agent::Checkpoint#initialize—original_input:is now a required keyword argument: Applications that constructCheckpointinstances directly must addoriginal_input: input. Checkpoints produced by#invokealready include this field automatically.
Fixed
ReactAgent#step— system instructions were never applied: The first iteration of the ReAct loop now callsbuild_contextto assemble the system prompt and history, matching the behaviour ofAgent::Base. Subsequent iterations re-apply instructions viabuild_cached_system_textbefore callingchat.complete. Previously, all iterations silently omitted the system prompt.Agent::Base#resume— system instructions were not re-applied after suspension: Resuming from aCheckpointnow callsbuild_cached_system_textusing the original input stored in the checkpoint, so the LLM receives the correct system prompt when the conversation continues. Previously, the LLM was called without any system instructions on resume.
[0.4.0] - 2026-05-19
Removed
Phronomy::TrustPipelineremoved: TheTrustPipelineclass and its innerTrustResultvalue object have been deleted. UsePhronomy::GeneratorVerifierinstead, which provides the same generator-verifier pattern with a cleaner, fully injectable API.
Added
Phronomy::GeneratorVerifier— Generator-Verifier coordination loop (Anthropic blog, Pattern 1). Wraps a generator agent and a verifier agent with fully injectable prompt builders, response parsers, a configurable iteration limit, and an approval-outcome raise policy.Phronomy::Agent::Orchestrator— Base class for orchestrator agents (Anthropic blog, Pattern 2). ExtendsAgent::Basewith asubagentDSL for declarative subagent registration as LLM-callable tools, plusdispatch_parallelandfan_outfor programmatic parallel invocation.Phronomy::Agent::TeamCoordinator— Agent teams coordination pattern (Anthropic blog, Pattern 3). An LLM-powered coordinator with a shared task queue and a pool of worker agents that carry conversation history across task assignments. Addscoordinator_providerDSL for independent LLM routing.Phronomy::Agent::SharedState— Shared-state coordination pattern (Anthropic blog, Pattern 5). Peer agents collaborate via aKnowledgeStore; thememberDSL registers agents with per-agent instructions;coordinationsets the team protocol;build_promptinjects a tool-usage guide automatically.Phronomy::LowConfidenceError— Exception raised byGeneratorVerifierwhenraise_policy: :raiseand verification fails after exhausting the iteration limit.
Changed
Phronomy::Graph::StateGraphevent system refactored: Per-nodeadvanceevents replaced with a unifiednode_completedevent queue, reducing event-handler registration overhead and simplifying listener registration.
[0.3.0] - 2026-05-18
Removed
Phronomy::Memorymodule fully removed:ConversationManager, allStoragebackends (InMemory, ActiveRecord), allRetrievalstrategies (Recent, Semantic, Composite), and allCompressionhelpers (ToolOutputPruner, Summary) have been deleted. Conversation history is now the responsibility of the calling application — pass prior messages viaconfig[:messages](Array<RubyLLM::Message>) and receive the updated array inresult[:messages].Phronomy::StateStoremodule fully removed:InMemory,ActiveRecord,Redis, andFileSystemstate-store backends have been deleted. The Workflow halted-state object is now returned directly frominvokeandsend_eventand must be stored by the caller if resumption is needed.Phronomy::Configuration#default_state_storeremoved: No longer meaningful without a built-in state store.Phronomy::Configuration#default_memory/#memory_async/#memory_job_queueremoved: No longer meaningful without the Memory module.- Rails integration removed:
Railtieinitializers forAgentJobandacts_as_phronomy_messageno longer load. Therails/andactive_record/directories have been deleted. Phronomy::ActorandPhronomy::ThreadActorRegistrydeleted: The Active Object pattern implementation (actor.rb,thread_actor_registry.rb) has been removed. It provided synchronous blocking only (no true async) and was architecturally inconsistent with theWorkflowRunnerhalt/resume model. All thread coordination now uses plainMutexwhere needed.Phronomy.configuration.max_actorsremoved: The configuration option is no longer meaningful withoutThreadActorRegistry.
Changed
Agent::Base#invokeand#streamno longer route execution through a per-thread Actor. Both methods now call_invoke_impl/_stream_impldirectly on the calling thread.Memory::Storage::InMemorynow stores all thread data in an instance-levelHashinstead ofThread.currentthread-local storage. The class-levelTHREAD_DATA_KEYconstant has been removed.with_thread_lockuses a per-thread-idMutexto preserve concurrent-compaction safety (issue #44).StateStore::InMemorynow stores state in an instance-levelHash. TheTHREAD_DATA_KEYconstant has been removed.VectorStore::RedisSearchuses aMutexforensure_index!andclearinstead of an Actor, preserving the thread-safety invariant on@index_created.Tool::McpTool::StdioTransport,Tracing::LangfuseTracer,TrustPipeline, andMemory::Retrieval::Semanticno longer hold a dedicated Actor instance. All operations execute directly on the calling thread.PIIPatternDetector—:my_numberreplaced by:ssn([#77]): The built-in PII detector now checks for US Social Security Numbers (\b\d{3}-\d{2}-\d{4}\b) instead of Japanese My Numbers. The JIS X 0076 check-digit validation andmy_number_valid?helper have been removed. Category key renamed from:my_numberto:ssn.PIIPatternDetector— phone pattern updated to international format ([#77]): The:phonepattern now matches 3-digit area code + 3–4-digit exchange + 4-digit subscriber number with optional E.164 country-code prefix ((?:\+\d{1,3}[.\- ]?)?\(?\d{3}\)?[.\- ]?\d{3,4}[.\- ]?\d{4}\b), replacing the previous Japan-specific pattern.
Fixed
RubyLLM::Providers::OpenAI#handle_error_chunk—NoMethodErroron single-line SSE error chunks: Some models (e.g. Qwen running via LM Studio) return SSE error events as a single line (data: {...}) without a precedingevent:line. The upstream implementation calledchunk.split("\n")[1].delete_prefix(...), which raisedNoMethodError: undefined method 'delete_prefix' for nilwhen the second element was absent. A monkey-patch inlib/phronomy/ruby_llm_patches.rbguards against this by returning an empty string when the split result has fewer than two elements.README— stale Memory API examples ([#76]): All references to the non-existentWindowMemory,ActiveRecordMemory,SemanticMemoryclasses andload_messages/memory_compressionAPI have been replaced with the correctConversationManager-based API.README—PIIPatternDetectorcomment ([#77]): Inline comment updated to# Detect SSNs, credit cards, emails, and phone numbers.README— Configuration block markdown ([#80]): Themax_actorsNote block was incorrectly placed inside the Ruby code fence; moved outside so it renders as a blockquote.README—Guardrailsstability label ([#76]): Changed fromStabletoBetato reflect that the built-in detector patterns may evolve.CHANGELOG— stale entries ([#78]): Removed the orphaned[Unreleased]section describing a never-released API, and replaced a forward"As of 0.3.0"reference with future-tense wording.McpTool— YARD class comment ([#79]): Updated to document both thestdio://andhttp:///https://transport schemes.README—max_actorsconfiguration reference ([#80]): Addedc.max_actorsexample and LRU eviction note to the Configuration section.
[0.2.2] - 2026-05-17
Fixed
Tool::Basetype validation — strict mode (#73): Removed string-coercion pass-through for:integer,:number/:float, and:booleanparameters. AStringvalue such as"42"now correctly raises a type error regardless of theon_schema_errormode. Fixes silent data corruption where the raw string was forwarded toexecuteinstead of the expected numeric/boolean type.README— correctsend_eventAPI example (#69): Fixed code sample that calledapp.send_event(:approve, config: { thread_id: ... })(positional args) which raisesArgumentErrorat runtime. Corrected toapp.send_event(state: state, event: :approve).phronomy.gemspec— excludevendor/from gem package (#70):vendor/bundle(~3 500 files, ~14 MB) was included in released gems. Added"vendor/"to the file reject list.
Added
Phronomy::Configuration#max_actors(#72): New optional attribute (defaultnil= unlimited, backward-compatible). When set,ThreadActorRegistryenforces an LRU eviction policy: the least-recently-used actor is stopped and removed before a new one is created, preventing unbounded thread growth in long-running processes.README— feature stability table (#71): Features section now uses a table with Stable / Beta / Experimental labels so users can assess maturity at a glance.TrustPipeline::Result#citations— unverified-source warning (#74): YARD documentation now explicitly states that citations are extracted from the LLM's own output and have not been verified against any external source.
CI
- Ruby 3.4 added to CI matrix (#75): Aligns test coverage with the gemspec
requirement (
>= 3.2.0) and verifies compatibility with the current stable Ruby release.
[0.2.1] — Unreleased
Changed
WorkflowRunner— state_machines fully drives execution (architecture overhaul). Previouslystate_machineswas used only for post-hoc transition validation; the next-node was calculated by Phronomy internally (resolve_next_node). After this change, all state transition decisions — including guard evaluation for routing events — will be delegated entirely tostate_machines.PhaseTrackernow exposesattr_accessor :contextso guard lambdas can access theWorkflowContextviam.context.- Guard bridge pattern:
if: ->(m) { guard_proc.call(m.context) }. - Three event types registered per workflow:
advance_<from>— unconditional after-transitions<routing_event>— guarded branching from action states (name is the event name used in the DSL, e.g.:route,:route_review)<external_event>— human-in-the-loop triggers from wait states- Invalid transitions now raise
ArgumentErrorinstead of logging warnings.
WorkflowRunnerinitializer signature changed —edges:,conditional_edges:, andwait_states:replaced byafter_transitions:,route_transitions:,external_events:, andwait_state_names:. This is an internal-only change; the publicPhronomy::Workflow.defineDSL is unchanged.
Removed (internal)
WorkflowRunner#resolve_next_node— logic moved to state_machinesWorkflowRunner#advance_phase— replaced byfire_event!Workflow::Builder#build_edges,#build_conditional_edges,#build_wait_states— replaced by unified event classification inbuild
[0.2.0] - 2026-05-13
Added
Phronomy::Graph::WorkflowRunner— state_machines-based execution engine (introduced as the internal successor toCompiledGraph).state.phase— single source of truth for graph execution state (replacescurrent_nodes+halted_beforedual attributes).state.halted?— returnstruewhen the graph is paused.CompiledGraph#add_wait_state— declared a named wait state that halts automatically when reached (later superseded bywait_stateDSL inWorkflow.define).CompiledGraph#send_event(state:, event:, input: nil)— event-driven resume API (later superseded byapp.send_event).
Removed
ParallelNodeandadd_parallel_nodeDSL. UseThread.neworConcurrent::Futureat the application level instead.Phronomy::Graph::TimeoutError(was only used byParallelNode).