phronomy 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +8 -9
  3. data/CHANGELOG.md +151 -1134
  4. data/CONTRIBUTING.md +28 -16
  5. data/README.md +69 -1249
  6. data/benchmark/baseline.json +2 -3
  7. data/benchmark/bench_agent_invoke.rb +4 -4
  8. data/benchmark/bench_context_assembler.rb +134 -34
  9. data/benchmark/bench_regression.rb +26 -6
  10. data/benchmark/bench_tool_schema.rb +2 -35
  11. data/docs/changelog/0.14-and-earlier.md +1137 -0
  12. data/docs/decisions/005-static-knowledge-class-level-cache.md +12 -1
  13. data/docs/decisions/008-orchestrator-uses-os-threads.md +46 -48
  14. data/docs/decisions/010-cooperative-first-concurrency.md +155 -228
  15. data/docs/decisions/011-build-context-as-single-llm-input-authority.md +2 -2
  16. data/docs/decisions/013-journal-backed-knowledge-as-context-candidates.md +122 -0
  17. data/docs/features.md +87 -0
  18. data/docs/getting-started.md +351 -0
  19. data/docs/migrations/0.15.md +35 -0
  20. data/docs/migrations/0.16.md +43 -0
  21. data/docs/runtime-and-concurrency.md +258 -0
  22. data/examples/workflows/generic_task_event_mapping.rb +14 -6
  23. data/lib/phronomy/agent/agent_invocation.rb +2 -36
  24. data/lib/phronomy/agent/agent_invocation_session_builder.rb +157 -94
  25. data/lib/phronomy/agent/agent_root.rb +1 -2
  26. data/lib/phronomy/agent/async_event_api.rb +3 -3
  27. data/lib/phronomy/agent/base.rb +170 -333
  28. data/lib/phronomy/agent/context/capability/base.rb +177 -298
  29. data/lib/phronomy/agent/context_assembler.rb +65 -29
  30. data/lib/phronomy/agent/context_parts/unit_builders/dependency_aware_unit_builder.rb +19 -89
  31. data/lib/phronomy/agent/context_plan_validator.rb +0 -33
  32. data/lib/phronomy/agent/execution_coordinator.rb +6 -7
  33. data/lib/phronomy/agent/journal_projection.rb +28 -2
  34. data/lib/phronomy/agent/ruby_llm_materializer.rb +2 -111
  35. data/lib/phronomy/agent/shared_state.rb +48 -138
  36. data/lib/phronomy/agent/token_budget_resolver.rb +5 -4
  37. data/lib/phronomy/agent/tool_executor.rb +29 -71
  38. data/lib/phronomy/agent/tool_invocation.rb +180 -336
  39. data/lib/phronomy/agent/tool_invocation_session_builder.rb +55 -161
  40. data/lib/phronomy/agent.rb +6 -10
  41. data/lib/phronomy/configuration.rb +4 -171
  42. data/lib/phronomy/diagnostics.rb +12 -41
  43. data/lib/phronomy/engine/concurrency/async_queue.rb +5 -188
  44. data/lib/phronomy/engine/concurrency/cancellation_scope.rb +6 -7
  45. data/lib/phronomy/engine/concurrency/cancellation_token.rb +51 -79
  46. data/lib/phronomy/engine/concurrency/deadline.rb +2 -3
  47. data/lib/phronomy/engine/concurrency/offload_pool.rb +696 -0
  48. data/lib/phronomy/engine/concurrency/pool_registry.rb +5 -5
  49. data/lib/phronomy/engine/event_loop.rb +89 -190
  50. data/lib/phronomy/engine/runtime/timer_queue.rb +48 -71
  51. data/lib/phronomy/engine/runtime/timer_service.rb +13 -21
  52. data/lib/phronomy/engine/runtime.rb +49 -377
  53. data/lib/phronomy/engine/task.rb +136 -277
  54. data/lib/phronomy/llm_adapter/base.rb +14 -14
  55. data/lib/phronomy/llm_adapter/ruby_llm.rb +3 -4
  56. data/lib/phronomy/llm_adapter.rb +2 -2
  57. data/lib/phronomy/llm_context_window/token_budget.rb +8 -79
  58. data/lib/phronomy/metrics.rb +15 -30
  59. data/lib/phronomy/multi_agent/fan_out_invocation.rb +146 -0
  60. data/lib/phronomy/multi_agent/fan_out_session_builder.rb +125 -0
  61. data/lib/phronomy/multi_agent/handoff.rb +1 -0
  62. data/lib/phronomy/multi_agent/orchestrator.rb +252 -256
  63. data/lib/phronomy/multi_agent/team_coordinator.rb +44 -133
  64. data/lib/phronomy/testing/eval/comparison.rb +23 -0
  65. data/lib/phronomy/testing/eval/dataset.rb +27 -0
  66. data/lib/phronomy/testing/eval/eval_case.rb +13 -0
  67. data/lib/phronomy/testing/eval/eval_result.rb +16 -0
  68. data/lib/phronomy/testing/eval/metrics.rb +43 -0
  69. data/lib/phronomy/testing/eval/runner.rb +52 -0
  70. data/lib/phronomy/testing/eval/scorer/base.rb +15 -0
  71. data/lib/phronomy/testing/eval/scorer/exact_match.rb +25 -0
  72. data/lib/phronomy/testing/eval/scorer/includes_scorer.rb +25 -0
  73. data/lib/phronomy/testing/eval/scorer/llm_judge.rb +46 -0
  74. data/lib/phronomy/testing/eval/scorer.rb +10 -0
  75. data/lib/phronomy/testing/eval.rb +9 -0
  76. data/lib/phronomy/testing/fake_clock.rb +6 -53
  77. data/lib/phronomy/testing.rb +2 -6
  78. data/lib/phronomy/tools/agent.rb +141 -6
  79. data/lib/phronomy/vector_store/async_backend.rb +21 -17
  80. data/lib/phronomy/vector_store/base.rb +2 -2
  81. data/lib/phronomy/vector_store/embeddings/base.rb +6 -5
  82. data/lib/phronomy/vector_store/in_memory.rb +2 -2
  83. data/lib/phronomy/version.rb +1 -1
  84. data/lib/phronomy/workflow_runner.rb +2 -4
  85. data/lib/phronomy.rb +7 -121
  86. data/scripts/api_snapshot.rb +4 -15
  87. metadata +24 -38
  88. data/lib/phronomy/agent/context/knowledge/base.rb +0 -58
  89. data/lib/phronomy/agent/context/knowledge/entity_knowledge.rb +0 -102
  90. data/lib/phronomy/agent/context/knowledge/static_knowledge.rb +0 -58
  91. data/lib/phronomy/agent/fsm_runtime_adapter.rb +0 -210
  92. data/lib/phronomy/engine/concurrency/blocking_adapter_pool.rb +0 -561
  93. data/lib/phronomy/engine/runtime/deterministic_scheduler.rb +0 -439
  94. data/lib/phronomy/engine/runtime/fake_scheduler.rb +0 -165
  95. data/lib/phronomy/engine/runtime/runtime_metrics.rb +0 -116
  96. data/lib/phronomy/engine/runtime/scheduler.rb +0 -98
  97. data/lib/phronomy/engine/runtime/scheduler_timer_adapter.rb +0 -79
  98. data/lib/phronomy/engine/runtime/task_registry.rb +0 -95
  99. data/lib/phronomy/engine/runtime/thread_scheduler.rb +0 -30
  100. data/lib/phronomy/engine/task/backend.rb +0 -80
  101. data/lib/phronomy/engine/task/deferred_backend.rb +0 -73
  102. data/lib/phronomy/engine/task/fiber_backend.rb +0 -157
  103. data/lib/phronomy/engine/task/immediate_backend.rb +0 -89
  104. data/lib/phronomy/engine/task/mapped_backend.rb +0 -90
  105. data/lib/phronomy/engine/task/thread_backend.rb +0 -84
  106. data/lib/phronomy/engine/task_group.rb +0 -265
  107. data/lib/phronomy/eval/comparison.rb +0 -47
  108. data/lib/phronomy/eval/dataset.rb +0 -45
  109. data/lib/phronomy/eval/eval_case.rb +0 -17
  110. data/lib/phronomy/eval/eval_result.rb +0 -29
  111. data/lib/phronomy/eval/metrics.rb +0 -66
  112. data/lib/phronomy/eval/runner.rb +0 -94
  113. data/lib/phronomy/eval/scorer/base.rb +0 -22
  114. data/lib/phronomy/eval/scorer/exact_match.rb +0 -31
  115. data/lib/phronomy/eval/scorer/includes_scorer.rb +0 -32
  116. data/lib/phronomy/eval/scorer/llm_judge.rb +0 -72
  117. data/lib/phronomy/eval/scorer.rb +0 -9
  118. data/lib/phronomy/eval.rb +0 -7
  119. data/lib/phronomy/knowledge_source.rb +0 -12
  120. data/lib/phronomy/llm_context_window/assembler.rb +0 -191
  121. data/lib/phronomy/llm_context_window/context_version_cache.rb +0 -52
  122. data/lib/phronomy/testing/fake_scheduler.rb +0 -104
  123. data/lib/phronomy/testing/scheduler_helpers.rb +0 -68
data/lib/phronomy.rb CHANGED
@@ -5,36 +5,18 @@ require "ruby_llm"
5
5
  require_relative "phronomy/ruby_llm_patches"
6
6
 
7
7
  loader = Zeitwerk::Loader.for_gem
8
- # Teach Zeitwerk that "llm" maps to "LLM" so that file names such as
9
- # ruby_llm_embeddings.rb resolve to RubyLLMEmbeddings (not RubyLlmEmbeddings).
10
8
  loader.inflector.inflect("ruby_llm_embeddings" => "RubyLLMEmbeddings")
11
- # RAG: Zeitwerk would infer "Rag" — override to "RAG".
12
9
  loader.inflector.inflect("rag" => "RAG")
13
- # FSMSession: Zeitwerk would infer "FsmSession" — override to "FSMSession".
14
- # Phronomy::FSMSession is the top-level cooperative execution engine shared by
15
- # WorkflowRunner, AgentInvocationSessionBuilder, and ToolInvocationSessionBuilder.
16
10
  loader.inflector.inflect("fsm_session" => "FSMSession")
17
- # LLMAdapter: Zeitwerk would infer "LlmAdapter" — override to "LLMAdapter".
18
11
  loader.inflector.inflect("llm_adapter" => "LLMAdapter")
19
- # LLMOperationResult: preserve the LLM acronym for the Agent result carrier.
20
12
  loader.inflector.inflect("llm_operation_result" => "LLMOperationResult")
21
- # LLMAdapter::RubyLLM: "ruby_llm" maps to "RubyLLM" (not "RubyLlm").
22
13
  loader.inflector.inflect("ruby_llm" => "RubyLLM")
23
- # CanonicalJSON: preserve uppercase JSON acronym.
24
14
  loader.inflector.inflect("canonical_json" => "CanonicalJSON")
25
- # RubyLLMMaterializer: preserve the double-uppercase LLM acronym.
26
15
  loader.inflector.inflect("ruby_llm_materializer" => "RubyLLMMaterializer")
27
- # LLMCallRecord: preserve uppercase LLM acronym.
28
16
  loader.inflector.inflect("llm_call_record" => "LLMCallRecord")
29
- # LLMInputManifest: preserve uppercase LLM acronym.
30
17
  loader.inflector.inflect("llm_input_manifest" => "LLMInputManifest")
31
- # LLMInputBuildContext / LLMInputPatch: preserve uppercase LLM acronym.
32
18
  loader.inflector.inflect("llm_input_build_context" => "LLMInputBuildContext")
33
19
  loader.inflector.inflect("llm_input_patch" => "LLMInputPatch")
34
- # Collapse engine/ so that its contents autoload directly under Phronomy::
35
- # (no Engine:: prefix). e.g. engine/event_loop.rb => Phronomy::EventLoop.
36
- # This allows the execution engine to be organised in its own subdirectory
37
- # without changing any class names or callers.
38
20
  loader.collapse("#{__dir__}/phronomy/engine")
39
21
  loader.setup
40
22
 
@@ -42,70 +24,31 @@ require_relative "phronomy/version"
42
24
  require_relative "phronomy/token_usage"
43
25
 
44
26
  module Phronomy
45
- # Exception hierarchy
46
27
  class Error < StandardError; end
47
28
  class ParseError < Error; end
48
29
  class RecursionLimitError < Error; end
49
30
  class ToolError < Error; end
50
- # Base error for Phronomy-owned timed boundaries and generic timeout primitives.
51
31
  class TimeoutError < Error; end
52
-
53
32
  class ConfigurationError < Error; end
54
-
55
33
  class HandoffError < Error; end
56
34
 
57
- # Raised when a network or transport layer call fails (e.g. LLM API unreachable,
58
- # MCP server connection refused). Distinguishable from application-level errors
59
- # so callers can apply network-specific retry logic.
60
35
  class TransportError < Error; end
61
-
62
- # Raised when the LLM API returns a rate-limit response (HTTP 429 or equivalent).
63
- # Callers should back off and retry after the indicated delay.
64
36
  class RateLimitError < TransportError; end
65
-
66
- # Raised when the LLM API rejects the request due to an invalid or revoked API key.
67
- # Callers should not retry without fixing the credentials.
68
37
  class AuthenticationError < TransportError; end
69
-
70
- # Raised when the prompt exceeds the model's context window limit.
71
38
  class ContextLengthError < Error; end
72
-
73
- # Raised when a workflow or agent execution is explicitly cancelled.
74
- # Separate from TimeoutError (deadline exceeded) — this is an intentional stop.
75
39
  class CancellationError < Error; end
76
40
 
77
- # Raised when {Agent#invoke} (a synchronous, blocking call) is attempted from
78
- # inside an active scheduler task and +strict_runtime_guards+ is enabled.
79
- #
80
- # Calling a blocking invocation from within a scheduler task stalls the
81
- # scheduler until the inner invocation completes, preventing other tasks from
82
- # making progress (hidden deadlock risk). Use {Agent#invoke_async} followed by
83
- # +#await+ inside scheduler tasks instead.
84
- #
85
- # This error is only raised when:
86
- # Phronomy.configure { |c| c.strict_runtime_guards = true }
87
- #
88
- # By default a warning is logged and execution continues.
89
- #
90
- # @see Phronomy::Runtime.in_scheduler_context?
91
- class SchedulerReentrancyError < Error; end
92
-
93
- # Raised when work is submitted to a Runtime whose shutdown has begun, or
94
- # when a Runtime cannot be reset because owned resources are still alive.
95
- class RuntimeShutdownError < Error; end
41
+ # Raised when a synchronous API would block the EventLoop control thread.
42
+ class EventLoopReentrancyError < Error; end
43
+
44
+ # Backward-compatible error class name for callers that still rescue the old
45
+ # scheduler-oriented exception. New code should use EventLoopReentrancyError.
46
+ class SchedulerReentrancyError < EventLoopReentrancyError; end
96
47
 
97
- # Raised when Runtime#shutdown is invoked from inside a Phronomy::Task.
48
+ class RuntimeShutdownError < Error; end
98
49
  class RuntimeShutdownReentrancyError < RuntimeShutdownError; end
99
50
 
100
- # Raised by {Phronomy::GeneratorVerifier#invoke} when +raise_if_untrusted: true+
101
- # and the pipeline's combined confidence score falls below the configured threshold.
102
- #
103
- # @example
104
- # rescue Phronomy::LowConfidenceError => e
105
- # puts e.result.confidence # => e.g. 0.45
106
- # puts e.result.output # best-effort answer despite low confidence
107
51
  class LowConfidenceError < Error
108
- # @return [Phronomy::GeneratorVerifier::Result] the untrusted result
109
52
  attr_reader :result
110
53
 
111
54
  def initialize(result)
@@ -114,9 +57,6 @@ module Phronomy
114
57
  end
115
58
  end
116
59
 
117
- # Raised by a {Phronomy::Filter::Base} subclass when the filter rejects a
118
- # value without transforming it (blocking the pipeline).
119
- # @api public
120
60
  class FilterBlockError < Error
121
61
  attr_reader :filter
122
62
 
@@ -126,27 +66,9 @@ module Phronomy
126
66
  end
127
67
  end
128
68
 
129
- # Raised when an operation is submitted to a {BlockingAdapterPool} that has
130
- # already been shut down via {BlockingAdapterPool#shutdown}.
131
69
  class PoolShutdownError < Error; end
132
-
133
- # Raised when a concurrency limit is exceeded and the configured backpressure
134
- # strategy is +:raise+. The caller should back off and retry.
135
70
  class BackpressureError < Error; end
136
71
 
137
- # Raised by {CancellationScope#pop_queue} when the deadline expires before a
138
- # result is available. Extends {TimeoutError} for backwards compatibility.
139
- class ScopeTimeoutError < TimeoutError; end
140
-
141
- # Deprecated compatibility constant. Workflow entry/exit actions are
142
- # synchronous and the Workflow DSL no longer accepts +action_timeout:+.
143
- class ActionTimeoutError < TimeoutError; end
144
-
145
- # Raised when a {Phronomy::WorkflowContext} field is mutated from a thread
146
- # that does not own the context (i.e. not the EventLoop dispatch thread).
147
- # Only raised in EventLoop mode. Use +context.merge(...)+ to produce a new
148
- # context, or deliver updates as +:action_completed+ event payloads
149
- # via {Agent::Base#invoke_async} + {Task#map}.
150
72
  class WorkflowContextOwnershipError < Error; end
151
73
 
152
74
  class << self
@@ -158,36 +80,10 @@ module Phronomy
158
80
  yield configuration
159
81
  end
160
82
 
161
- # Resets the global Phronomy configuration to defaults.
162
- #
163
- # **Intended for test suites only.** Calling this in a production process
164
- # will drop all runtime configuration (tracer, model, tokenizer, etc.)
165
- # globally and immediately affect all subsequent agent and workflow calls.
166
- #
167
- # **Parallel test suites warning:** When tests run in parallel (e.g.
168
- # `parallel_tests` or `parallel_rspec`), +reset_configuration!+ in one
169
- # worker will clear configuration shared with other workers in the same
170
- # process. Prefer process-isolation strategies (forked workers) over
171
- # thread-based parallelism when using this method.
172
- #
173
- # Typical usage in a sequential test suite:
174
- # after { Phronomy.reset_configuration! }
175
83
  def reset_configuration!
176
84
  @configuration = Configuration.new
177
85
  end
178
86
 
179
- # Yields the current {Configuration} object, then restores the original
180
- # configuration on exit (even if the block raises).
181
- #
182
- # Intended for test helpers that need to temporarily override settings
183
- # without permanently mutating the global configuration.
184
- #
185
- # @yield [config] the current {Configuration} instance (mutable)
186
- # @example
187
- # Phronomy.with_configuration do |c|
188
- # c.logger = Logger.new($stdout)
189
- # end
190
- # @api public
191
87
  def with_configuration
192
88
  original = @configuration&.dup
193
89
  yield configuration
@@ -195,16 +91,6 @@ module Phronomy
195
91
  @configuration = original
196
92
  end
197
93
 
198
- # Shuts down and clears the process-wide default Runtime, then resets
199
- # global configuration. Intended for test suites only.
200
- #
201
- # Runtime execution failure and resource cleanup are separate. The
202
- # singleton is cleared when cleanup completed, even if execution failed.
203
- #
204
- # @param timeout [Numeric] maximum graceful wait for Runtime tasks and
205
- # EventLoop shutdown
206
- # @return [Phronomy::Runtime::ShutdownResult]
207
- # @api public
208
94
  def reset_runtime!(timeout: configuration.event_loop_stop_grace_seconds)
209
95
  previous_grace = @configuration&.event_loop_stop_grace_seconds
210
96
  result = Runtime.reset_default!(timeout: timeout)
@@ -3,24 +3,19 @@
3
3
 
4
4
  # scripts/api_snapshot.rb
5
5
  #
6
- # Dumps the public instance methods of all Stable/Beta public API classes to
7
- # JSON. The snapshot is stored in spec/fixtures/api_snapshot.json and is used
8
- # by spec/phronomy/api_compatibility_spec.rb to detect unintended API removals.
6
+ # Dumps the public instance methods of all Stable/Beta product API classes to
7
+ # JSON. Testing helpers are intentionally excluded from this compatibility gate.
8
+ # The snapshot is stored in spec/fixtures/api_snapshot.json and is used by
9
+ # spec/phronomy/api_compatibility_spec.rb to detect unintended API removals.
9
10
  #
10
11
  # Usage:
11
- # # Regenerate spec/fixtures/api_snapshot.json (run when intentionally adding
12
- # # or removing public API methods after updating the stability table):
13
12
  # ruby scripts/api_snapshot.rb --write
14
- #
15
- # # Print snapshot to stdout (useful for manual inspection):
16
13
  # ruby scripts/api_snapshot.rb
17
14
 
18
15
  require "json"
19
16
  require "fileutils"
20
17
  require_relative "../lib/phronomy"
21
18
 
22
- # Classes and modules whose public API is tracked.
23
- # Add an entry whenever a new class/module is promoted to Stable or Beta in README.md.
24
19
  PUBLIC_API_ENTRIES = [
25
20
  # Stable
26
21
  Phronomy::Agent::Base,
@@ -37,17 +32,13 @@ PUBLIC_API_ENTRIES = [
37
32
  Phronomy::VectorStore::Base,
38
33
  Phronomy::VectorStore::InMemory,
39
34
  Phronomy::VectorStore::Embeddings::Base,
40
- Phronomy::Agent::Context::Knowledge::Base,
41
- Phronomy::Agent::Context::Knowledge::StaticKnowledge,
42
35
  Phronomy::Tracing::Base,
43
36
  Phronomy::Tracing::NullTracer,
44
- Phronomy::Eval::Runner,
45
37
  Phronomy::Tools::Mcp,
46
38
  Phronomy::Tools::Agent,
47
39
  Phronomy::Tools::VectorSearch
48
40
  ].freeze
49
41
 
50
- # Baseline methods common to all Ruby objects — excluded from the snapshot.
51
42
  BASELINE_INSTANCE_METHODS = (
52
43
  Object.public_instance_methods |
53
44
  Kernel.public_instance_methods
@@ -60,7 +51,6 @@ BASELINE_CLASS_METHODS = (
60
51
 
61
52
  def snapshot_entry(klass)
62
53
  if klass.instance_of?(Module)
63
- # Module — capture instance methods defined in this module only
64
54
  own_methods = klass.public_instance_methods(false).sort
65
55
  {
66
56
  "name" => klass.name,
@@ -68,7 +58,6 @@ def snapshot_entry(klass)
68
58
  "public_instance_methods" => own_methods
69
59
  }
70
60
  else
71
- # Class — capture public instance methods minus universal baseline
72
61
  instance_methods = (klass.public_instance_methods - BASELINE_INSTANCE_METHODS).sort
73
62
  class_methods = (klass.public_methods(false) - BASELINE_CLASS_METHODS).sort
74
63
  {
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: phronomy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.0
4
+ version: 0.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Raizo T.C.S
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-08-08 00:00:00.000000000 Z
11
+ date: 2026-08-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby_llm
@@ -104,6 +104,7 @@ files:
104
104
  - benchmark/bench_vector_store.rb
105
105
  - benchmark/bench_workflow.rb
106
106
  - benchmark/run_all.rb
107
+ - docs/changelog/0.14-and-earlier.md
107
108
  - docs/decisions/001-rubyllm-as-provider-layer.md
108
109
  - docs/decisions/002-workflow-context-immutability.md
109
110
  - docs/decisions/003-event-loop-singleton.md
@@ -117,7 +118,13 @@ files:
117
118
  - docs/decisions/011-build-context-as-single-llm-input-authority.md
118
119
  - docs/decisions/011-delegate-transport-policy-to-adapters.md
119
120
  - docs/decisions/012-canonical-execution-log-and-context-policy.md
121
+ - docs/decisions/013-journal-backed-knowledge-as-context-candidates.md
122
+ - docs/features.md
123
+ - docs/getting-started.md
120
124
  - docs/mcp-client.md
125
+ - docs/migrations/0.15.md
126
+ - docs/migrations/0.16.md
127
+ - docs/runtime-and-concurrency.md
121
128
  - examples/workflows/agent_event_mapping.rb
122
129
  - examples/workflows/generic_task_event_mapping.rb
123
130
  - gemfiles/mcp_1_0.gemfile
@@ -137,9 +144,6 @@ files:
137
144
  - lib/phronomy/agent/concerns/filterable.rb
138
145
  - lib/phronomy/agent/context/capability/base.rb
139
146
  - lib/phronomy/agent/context/instruction/prompt_template.rb
140
- - lib/phronomy/agent/context/knowledge/base.rb
141
- - lib/phronomy/agent/context/knowledge/entity_knowledge.rb
142
- - lib/phronomy/agent/context/knowledge/static_knowledge.rb
143
147
  - lib/phronomy/agent/context_assembler.rb
144
148
  - lib/phronomy/agent/context_candidate.rb
145
149
  - lib/phronomy/agent/context_candidate_resolver.rb
@@ -159,7 +163,6 @@ files:
159
163
  - lib/phronomy/agent/context_selection_unit.rb
160
164
  - lib/phronomy/agent/derived_content_spec.rb
161
165
  - lib/phronomy/agent/execution_coordinator.rb
162
- - lib/phronomy/agent/fsm_runtime_adapter.rb
163
166
  - lib/phronomy/agent/immutable.rb
164
167
  - lib/phronomy/agent/journal_projection.rb
165
168
  - lib/phronomy/agent/journal_record.rb
@@ -187,44 +190,18 @@ files:
187
190
  - lib/phronomy/context_budget_exceeded_error.rb
188
191
  - lib/phronomy/diagnostics.rb
189
192
  - lib/phronomy/engine/concurrency/async_queue.rb
190
- - lib/phronomy/engine/concurrency/blocking_adapter_pool.rb
191
193
  - lib/phronomy/engine/concurrency/cancellation_scope.rb
192
194
  - lib/phronomy/engine/concurrency/cancellation_token.rb
193
195
  - lib/phronomy/engine/concurrency/deadline.rb
196
+ - lib/phronomy/engine/concurrency/offload_pool.rb
194
197
  - lib/phronomy/engine/concurrency/pool_registry.rb
195
198
  - lib/phronomy/engine/event_loop.rb
196
199
  - lib/phronomy/engine/fsm_session.rb
197
200
  - lib/phronomy/engine/runtime.rb
198
- - lib/phronomy/engine/runtime/deterministic_scheduler.rb
199
- - lib/phronomy/engine/runtime/fake_scheduler.rb
200
- - lib/phronomy/engine/runtime/runtime_metrics.rb
201
- - lib/phronomy/engine/runtime/scheduler.rb
202
- - lib/phronomy/engine/runtime/scheduler_timer_adapter.rb
203
201
  - lib/phronomy/engine/runtime/shutdown_result.rb
204
- - lib/phronomy/engine/runtime/task_registry.rb
205
- - lib/phronomy/engine/runtime/thread_scheduler.rb
206
202
  - lib/phronomy/engine/runtime/timer_queue.rb
207
203
  - lib/phronomy/engine/runtime/timer_service.rb
208
204
  - lib/phronomy/engine/task.rb
209
- - lib/phronomy/engine/task/backend.rb
210
- - lib/phronomy/engine/task/deferred_backend.rb
211
- - lib/phronomy/engine/task/fiber_backend.rb
212
- - lib/phronomy/engine/task/immediate_backend.rb
213
- - lib/phronomy/engine/task/mapped_backend.rb
214
- - lib/phronomy/engine/task/thread_backend.rb
215
- - lib/phronomy/engine/task_group.rb
216
- - lib/phronomy/eval.rb
217
- - lib/phronomy/eval/comparison.rb
218
- - lib/phronomy/eval/dataset.rb
219
- - lib/phronomy/eval/eval_case.rb
220
- - lib/phronomy/eval/eval_result.rb
221
- - lib/phronomy/eval/metrics.rb
222
- - lib/phronomy/eval/runner.rb
223
- - lib/phronomy/eval/scorer.rb
224
- - lib/phronomy/eval/scorer/base.rb
225
- - lib/phronomy/eval/scorer/exact_match.rb
226
- - lib/phronomy/eval/scorer/includes_scorer.rb
227
- - lib/phronomy/eval/scorer/llm_judge.rb
228
205
  - lib/phronomy/event.rb
229
206
  - lib/phronomy/execution_rehydration_required_error.rb
230
207
  - lib/phronomy/filter.rb
@@ -236,15 +213,14 @@ files:
236
213
  - lib/phronomy/invalid_async_workflow_action_error.rb
237
214
  - lib/phronomy/invalid_context_budget_configuration_error.rb
238
215
  - lib/phronomy/invocation_context.rb
239
- - lib/phronomy/knowledge_source.rb
240
216
  - lib/phronomy/llm_adapter.rb
241
217
  - lib/phronomy/llm_adapter/base.rb
242
218
  - lib/phronomy/llm_adapter/ruby_llm.rb
243
- - lib/phronomy/llm_context_window/assembler.rb
244
- - lib/phronomy/llm_context_window/context_version_cache.rb
245
219
  - lib/phronomy/llm_context_window/token_budget.rb
246
220
  - lib/phronomy/llm_context_window/token_estimator.rb
247
221
  - lib/phronomy/metrics.rb
222
+ - lib/phronomy/multi_agent/fan_out_invocation.rb
223
+ - lib/phronomy/multi_agent/fan_out_session_builder.rb
248
224
  - lib/phronomy/multi_agent/handoff.rb
249
225
  - lib/phronomy/multi_agent/orchestrator.rb
250
226
  - lib/phronomy/multi_agent/parallel_tool_chat.rb
@@ -261,9 +237,19 @@ files:
261
237
  - lib/phronomy/state_store/in_memory.rb
262
238
  - lib/phronomy/stream_callback_error.rb
263
239
  - lib/phronomy/testing.rb
240
+ - lib/phronomy/testing/eval.rb
241
+ - lib/phronomy/testing/eval/comparison.rb
242
+ - lib/phronomy/testing/eval/dataset.rb
243
+ - lib/phronomy/testing/eval/eval_case.rb
244
+ - lib/phronomy/testing/eval/eval_result.rb
245
+ - lib/phronomy/testing/eval/metrics.rb
246
+ - lib/phronomy/testing/eval/runner.rb
247
+ - lib/phronomy/testing/eval/scorer.rb
248
+ - lib/phronomy/testing/eval/scorer/base.rb
249
+ - lib/phronomy/testing/eval/scorer/exact_match.rb
250
+ - lib/phronomy/testing/eval/scorer/includes_scorer.rb
251
+ - lib/phronomy/testing/eval/scorer/llm_judge.rb
264
252
  - lib/phronomy/testing/fake_clock.rb
265
- - lib/phronomy/testing/fake_scheduler.rb
266
- - lib/phronomy/testing/scheduler_helpers.rb
267
253
  - lib/phronomy/token_usage.rb
268
254
  - lib/phronomy/tools/agent.rb
269
255
  - lib/phronomy/tools/mcp.rb
@@ -1,58 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module Agent
5
- module Context
6
- module Knowledge
7
- # Abstract base class for all KnowledgeSource implementations.
8
- #
9
- # Subclasses must implement #fetch(query:) and return an Array of chunk Hashes.
10
- # Each chunk Hash must contain:
11
- # :content [String] the text to inject into the context
12
- # :type [Symbol] semantic tag (e.g. :static, :rag, :entity)
13
- class Base
14
- # Retrieve knowledge chunks relevant to the given query.
15
- #
16
- # @param query [String, nil] the current user input used to select relevant chunks
17
- # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil] optional token; raises CancellationError when cancelled
18
- # @return [Array<Hash>] array of { content: String, type: Symbol }
19
- # @api public
20
- def fetch(query: nil, cancellation_token: nil)
21
- cancellation_token&.raise_if_cancelled!
22
- raise NotImplementedError, "#{self.class}#fetch is not implemented"
23
- end
24
-
25
- # Submits a {#fetch} call to {BlockingAdapterPool} and returns a
26
- # {BlockingAdapterPool::PendingOperation}.
27
- # Callers can fan out multiple fetches in parallel and await them all.
28
- #
29
- # @param query [String, nil]
30
- # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
31
- # @param timeout [Numeric, nil] seconds before the operation is abandoned
32
- # @return [BlockingAdapterPool::PendingOperation]
33
- # @api public
34
- def fetch_async(query: nil, cancellation_token: nil, timeout: nil)
35
- Phronomy::Runtime.instance.blocking_io.submit(
36
- timeout: timeout,
37
- cancellation_token: cancellation_token
38
- ) do
39
- fetch(query: query, cancellation_token: cancellation_token)
40
- end
41
- end
42
-
43
- # Returns true when this source's content is considered static (i.e. does
44
- # not change between agent invocations). Static sources are eligible for
45
- # fingerprint-based caching in ContextVersionCache.
46
- #
47
- # Override in subclasses that return fixed content.
48
- #
49
- # @return [Boolean]
50
- # @api public
51
- def static?
52
- false
53
- end
54
- end
55
- end
56
- end
57
- end
58
- end
@@ -1,102 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module Agent
5
- module Context
6
- module Knowledge
7
- # A KnowledgeSource that extracts named-entity facts from conversation history.
8
- #
9
- # This is the knowledge-injection counterpart of the old EntityMemory.
10
- # It scans saved user messages with a regex heuristic (no LLM call) and
11
- # returns the discovered facts as a single knowledge chunk tagged :entity.
12
- #
13
- # EntityKnowledge is stateful: it accumulates extracted facts via #update(messages:)
14
- # which should be called each time new messages are saved.
15
- #
16
- # Supported extraction patterns (case-insensitive):
17
- # "my name is Alice" → { name: "Alice" }
18
- # "I am Alice" → { identity: "Alice" }
19
- # "I'm a software engineer" → { occupation: "software engineer" }
20
- # "I work at / for Acme" → { workplace: "Acme" }
21
- # "I live in Tokyo" → { location: "Tokyo" }
22
- # "I'm from Tokyo" → { location: "Tokyo" }
23
- # "I like / love Ruby" → { preference: "Ruby" }
24
- #
25
- # @example
26
- # ks = Phronomy::Agent::Context::Knowledge::EntityKnowledge.new
27
- # ks.update(messages: chat_messages)
28
- # agent = MyAgent.new
29
- # agent.add_knowledge_source(ks)
30
- # agent.invoke("What is my name?")
31
- class EntityKnowledge < Base
32
- PATTERNS = [
33
- [:name, /\bmy name is\s+([A-Za-z][A-Za-z0-9 \-']*)/i],
34
- [:identity, /\bI\s+am\s+([A-Z][A-Za-z0-9 \-']+)/],
35
- [:occupation, /\bI(?:'m| am) a(?:n)?\s+([A-Za-z][A-Za-z0-9 \-']*)/i],
36
- [:workplace, /\bI (?:work|worked) (?:at|for|in)\s+([A-Za-z0-9][A-Za-z0-9 \-'.&,]*)/i],
37
- [:location, /\bI live in\s+([A-Za-z][A-Za-z0-9 \-']*)/i],
38
- [:location, /\bI(?:'m| am) from\s+([A-Za-z][A-Za-z0-9 \-']*)/i],
39
- [:preference, /\bI (?:like|love|enjoy)\s+([A-Za-z][A-Za-z0-9 \-']*)/i]
40
- ].freeze
41
-
42
- def initialize
43
- @entities = {}
44
- end
45
-
46
- # Scan messages and accumulate entity facts.
47
- # Call this after saving a new set of messages (e.g. from a ConversationManager save hook).
48
- #
49
- # @param messages [Array] message objects responding to #role and #content
50
- # @api public
51
- def update(messages:)
52
- messages.each do |msg|
53
- next unless msg.role.to_sym == :user
54
-
55
- extract(msg.content.to_s).each { |key, value| @entities[key] = value }
56
- end
57
- end
58
-
59
- # Returns a single chunk containing all known entity facts in XML context format.
60
- # Returns an empty array when no entities have been discovered.
61
- #
62
- # @param query [String, nil] unused — entity knowledge is always fully injected
63
- # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil] optional; raises CancellationError when cancelled
64
- # @return [Array<Hash>]
65
- # @api public
66
- def fetch(query: nil, cancellation_token: nil)
67
- cancellation_token&.raise_if_cancelled!
68
- return [] if @entities.empty?
69
-
70
- lines = @entities.map { |key, value| "- #{key}: #{value}" }.join("\n")
71
- content = <<~CONTENT.chomp
72
- Known facts about the user:
73
- #{lines}
74
- CONTENT
75
- [{content: content, type: :entity}]
76
- end
77
-
78
- # Returns the current entity store (primarily for testing).
79
- #
80
- # @return [Hash]
81
- # @api public
82
- def entities
83
- @entities.dup
84
- end
85
-
86
- private
87
-
88
- def extract(text)
89
- found = {}
90
- PATTERNS.each do |key, pattern|
91
- if (match = text.match(pattern))
92
- value = match[1].strip.sub(/[.!?]\s+.*$/, "").gsub(/[.,;!?]+$/, "")
93
- found[key] = value unless value.empty?
94
- end
95
- end
96
- found
97
- end
98
- end
99
- end
100
- end
101
- end
102
- end
@@ -1,58 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module Agent
5
- module Context
6
- module Knowledge
7
- # A KnowledgeSource backed by fixed text provided at construction time.
8
- #
9
- # Useful for injecting static documents, policy files, or configuration
10
- # knowledge that does not change per request.
11
- #
12
- # @example
13
- # ks = Phronomy::Agent::Context::Knowledge::StaticKnowledge.new(
14
- # "Our refund policy: ...",
15
- # type: :policy
16
- # )
17
- # agent = MyAgent.new
18
- # agent.add_knowledge_source(ks)
19
- # agent.invoke("What is the refund policy?")
20
- class StaticKnowledge < Base
21
- # @param text [String] the static knowledge text to inject
22
- # @param type [Symbol] semantic tag for the chunk (default :static)
23
- # @param source [String, nil] label identifying where this knowledge came from
24
- # (e.g. a filename). Included in the context XML tag and exposed to the LLM
25
- # so that agents can produce grounded citations.
26
- # @api public
27
- def initialize(text, type: :static, source: nil)
28
- @text = text.to_s
29
- @type = type
30
- @source = source
31
- end
32
-
33
- # Returns the fixed text as a single chunk, regardless of query.
34
- #
35
- # @param query [String, nil] ignored for static knowledge
36
- # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil] optional; raises CancellationError when cancelled
37
- # @return [Array<Hash>]
38
- # @api public
39
- def fetch(query: nil, cancellation_token: nil)
40
- cancellation_token&.raise_if_cancelled!
41
- return [] if @text.empty?
42
-
43
- chunk = {content: @text, type: @type}
44
- chunk[:source] = @source if @source
45
- [chunk]
46
- end
47
-
48
- # Static knowledge content never changes between invocations.
49
- # @return [true]
50
- # @api public
51
- def static?
52
- true
53
- end
54
- end
55
- end
56
- end
57
- end
58
- end