phronomy 0.22.0 → 0.23.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 (156) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +3 -4
  3. data/CHANGELOG.md +200 -10
  4. data/CONTRIBUTING.md +81 -9
  5. data/README.md +15 -6
  6. data/VERIFY.sh +587 -0
  7. data/benchmark/bench_agent_invoke.rb +2 -2
  8. data/benchmark/bench_context_assembler.rb +39 -68
  9. data/benchmark/bench_regression.rb +2 -2
  10. data/docs/architecture/agent-context.md +174 -0
  11. data/docs/architecture/before-llm-input.md +78 -0
  12. data/docs/architecture/context-management.md +232 -0
  13. data/docs/architecture/knowledge-and-rag.md +130 -0
  14. data/docs/architecture/multi-agent-handoff.md +152 -0
  15. data/docs/architecture/persistence.md +175 -0
  16. data/docs/architecture/removed/agent-context.md +72 -0
  17. data/docs/architecture/security-boundaries.md +173 -0
  18. data/docs/architecture/tracing.md +194 -0
  19. data/docs/architecture.md +82 -0
  20. data/docs/archive/design/archived/04_api_design.md +507 -0
  21. data/docs/archive/design/archived/09_guardrails.md +186 -0
  22. data/docs/archive/design/archived/17_rails_integration.md +175 -0
  23. data/docs/archive/design/historical/00_design_philosophy.md +122 -0
  24. data/docs/archive/design/historical/01_rubyllm_evaluation.md +178 -0
  25. data/docs/archive/design/historical/06_design_decisions.md +143 -0
  26. data/docs/changelog/0.14-and-earlier.md +1 -1
  27. data/docs/decisions/001-rubyllm-as-provider-layer.md +6 -1
  28. data/docs/decisions/002-workflow-context-immutability.md +26 -1
  29. data/docs/decisions/006-no-built-in-guardrails.md +2 -1
  30. data/docs/decisions/012-canonical-execution-log-and-context-policy.md +120 -38
  31. data/docs/decisions/014-unified-persistence-durable-state.md +9 -2
  32. data/docs/decisions/016-semantic-multi-agent-handoff.md +112 -0
  33. data/docs/decisions/017-design-authority-and-adr-governance.md +200 -0
  34. data/docs/decisions/018-durability-guarantees-and-failure-model.md +488 -0
  35. data/docs/decisions/019-filter-contract-and-security-boundaries.md +229 -0
  36. data/docs/decisions/020-canonical-workflow-instance-identity.md +177 -0
  37. data/docs/decisions/021-generic-agent-invocation-identity-removal.md +119 -0
  38. data/docs/decisions/022-agent-execution-parent-identity-and-runtime-routing-boundary.md +193 -0
  39. data/docs/decisions/023-fsm-session-incarnation-identity-and-routing.md +139 -0
  40. data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +188 -0
  41. data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +249 -0
  42. data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +257 -0
  43. data/docs/decisions/027-llm-adapter-provider-boundary.md +93 -0
  44. data/docs/decisions/README.md +172 -0
  45. data/docs/features.md +31 -11
  46. data/docs/getting-started.md +77 -45
  47. data/docs/migrations/0.19.md +14 -7
  48. data/docs/migrations/0.22.md +390 -0
  49. data/docs/persistence-backends.md +88 -38
  50. data/docs/runtime-and-concurrency.md +227 -33
  51. data/examples/README.md +13 -0
  52. data/lib/phronomy/agent/agent_execution.rb +19 -15
  53. data/lib/phronomy/agent/agent_invocation.rb +288 -93
  54. data/lib/phronomy/agent/agent_invocation_session_builder.rb +236 -202
  55. data/lib/phronomy/agent/agent_root.rb +3 -3
  56. data/lib/phronomy/agent/approval_evaluation_request.rb +37 -19
  57. data/lib/phronomy/agent/async_event_api.rb +145 -72
  58. data/lib/phronomy/agent/base.rb +388 -181
  59. data/lib/phronomy/agent/concerns/before_llm_input.rb +1 -1
  60. data/lib/phronomy/agent/context_assembler.rb +437 -178
  61. data/lib/phronomy/agent/context_candidate_resolver.rb +2 -2
  62. data/lib/phronomy/agent/context_plan.rb +18 -13
  63. data/lib/phronomy/agent/context_plan_validator.rb +246 -88
  64. data/lib/phronomy/agent/context_policies/default.rb +123 -34
  65. data/lib/phronomy/agent/context_policy.rb +109 -3
  66. data/lib/phronomy/agent/context_policy_input.rb +244 -0
  67. data/lib/phronomy/agent/context_policy_input_builder.rb +241 -0
  68. data/lib/phronomy/agent/execution_coordinator.rb +1975 -587
  69. data/lib/phronomy/agent/journal_record.rb +17 -4
  70. data/lib/phronomy/agent/llm_input_build_context.rb +1 -1
  71. data/lib/phronomy/agent/llm_input_manifest.rb +277 -2
  72. data/lib/phronomy/agent/llm_operation_result.rb +12 -7
  73. data/lib/phronomy/agent/phase_machine_builder.rb +19 -7
  74. data/lib/phronomy/agent/provider_call_outcome.rb +23 -7
  75. data/lib/phronomy/agent/recovery_coordinator/continuation.rb +271 -0
  76. data/lib/phronomy/agent/recovery_coordinator/installation.rb +427 -0
  77. data/lib/phronomy/agent/recovery_coordinator/resolution.rb +635 -0
  78. data/lib/phronomy/agent/recovery_coordinator.rb +211 -0
  79. data/lib/phronomy/agent/recovery_support.rb +512 -0
  80. data/lib/phronomy/agent/ruby_llm_materializer.rb +22 -13
  81. data/lib/phronomy/agent/selection/candidate.rb +53 -0
  82. data/lib/phronomy/agent/selection/constraint.rb +49 -0
  83. data/lib/phronomy/agent/shared_state.rb +38 -1
  84. data/lib/phronomy/agent/tool_approval_request.rb +33 -5
  85. data/lib/phronomy/agent/tool_definition_set.rb +49 -3
  86. data/lib/phronomy/agent/tool_invocation.rb +336 -102
  87. data/lib/phronomy/agent/tool_invocation_session_builder.rb +49 -45
  88. data/lib/phronomy/agent.rb +20 -2
  89. data/lib/phronomy/agent_already_exists_error.rb +5 -0
  90. data/lib/phronomy/agent_purged_error.rb +5 -0
  91. data/lib/phronomy/engine/concurrency/offload_pool.rb +17 -3
  92. data/lib/phronomy/engine/concurrency/physical_completion_task.rb +135 -0
  93. data/lib/phronomy/engine/event_loop.rb +622 -63
  94. data/lib/phronomy/engine/fsm_session.rb +194 -21
  95. data/lib/phronomy/engine/runtime/agent_ownership_registry.rb +352 -0
  96. data/lib/phronomy/engine/runtime.rb +77 -20
  97. data/lib/phronomy/generator_verifier.rb +12 -14
  98. data/lib/phronomy/invocation_context.rb +9 -29
  99. data/lib/phronomy/multi_agent/admission_registry.rb +51 -0
  100. data/lib/phronomy/multi_agent/coordination_state.rb +18 -0
  101. data/lib/phronomy/multi_agent/coordinator.rb +154 -0
  102. data/lib/phronomy/multi_agent/execution_coordinator.rb +116 -0
  103. data/lib/phronomy/multi_agent/fan_out_invocation.rb +24 -33
  104. data/lib/phronomy/multi_agent/fan_out_session_builder.rb +12 -19
  105. data/lib/phronomy/multi_agent/handoff.rb +24 -45
  106. data/lib/phronomy/multi_agent/handoff_capability_factory.rb +87 -0
  107. data/lib/phronomy/multi_agent/handoff_context.rb +95 -0
  108. data/lib/phronomy/multi_agent/handoff_policy.rb +137 -0
  109. data/lib/phronomy/multi_agent/handoff_projection.rb +191 -0
  110. data/lib/phronomy/multi_agent/handoff_request.rb +45 -0
  111. data/lib/phronomy/multi_agent/orchestrator.rb +12 -15
  112. data/lib/phronomy/multi_agent/runner.rb +98 -0
  113. data/lib/phronomy/persistence/durable_codec.rb +646 -0
  114. data/lib/phronomy/persistence/durable_record.rb +117 -0
  115. data/lib/phronomy/persistence/in_memory.rb +210 -134
  116. data/lib/phronomy/persistence/migration/initial_format_migration.rb +226 -0
  117. data/lib/phronomy/persistence/repository_facades.rb +316 -0
  118. data/lib/phronomy/persistence.rb +81 -41
  119. data/lib/phronomy/recovery.rb +186 -0
  120. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +2 -2
  121. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +1 -1
  122. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +19 -19
  123. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +3 -3
  124. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +5 -5
  125. data/lib/phronomy/tracing/automatic.rb +176 -0
  126. data/lib/phronomy/tracing/base.rb +11 -2
  127. data/lib/phronomy/tracing/langfuse_tracer.rb +20 -12
  128. data/lib/phronomy/version.rb +1 -1
  129. data/lib/phronomy/workflow.rb +3 -6
  130. data/lib/phronomy/workflow_context.rb +14 -5
  131. data/lib/phronomy/workflow_recovery.rb +123 -0
  132. data/lib/phronomy/workflow_runner.rb +468 -256
  133. data/lib/phronomy.rb +6 -0
  134. data/scripts/api_snapshot.rb +12 -0
  135. data/sig/phronomy/agent.rbs +209 -7
  136. data/sig/phronomy/multi_agent.rbs +39 -0
  137. data/sig/phronomy/persistence.rbs +62 -4
  138. data/sig/phronomy/runtime.rbs +1 -4
  139. data/sig/phronomy/workflow.rbs +2 -2
  140. data/sig/phronomy.rbs +10 -0
  141. metadata +65 -17
  142. data/examples/workflows/agent_event_mapping.rb +0 -101
  143. data/examples/workflows/generic_task_event_mapping.rb +0 -66
  144. data/lib/phronomy/agent/activation_registry.rb +0 -28
  145. data/lib/phronomy/agent/agent_execution_activation.rb +0 -172
  146. data/lib/phronomy/agent/context_candidate.rb +0 -47
  147. data/lib/phronomy/agent/context_parts/budget/token_budget_packer.rb +0 -53
  148. data/lib/phronomy/agent/context_parts/requirements/required_context_resolver.rb +0 -56
  149. data/lib/phronomy/agent/context_parts/selectors/recent_first_selector.rb +0 -30
  150. data/lib/phronomy/agent/context_parts/unit_builders/dependency_aware_unit_builder.rb +0 -118
  151. data/lib/phronomy/agent/context_policy_descriptor.rb +0 -49
  152. data/lib/phronomy/agent/context_policy_registry.rb +0 -46
  153. data/lib/phronomy/agent/context_request.rb +0 -35
  154. data/lib/phronomy/agent/context_selection_unit.rb +0 -38
  155. data/lib/phronomy/agent/derived_content_spec.rb +0 -34
  156. data/lib/phronomy/agent/runner.rb +0 -97
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.22.0
4
+ version: 0.23.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-17 00:00:00.000000000 Z
11
+ date: 2026-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby_llm
@@ -95,6 +95,7 @@ files:
95
95
  - RELEASE_CHECKLIST.md
96
96
  - Rakefile
97
97
  - SECURITY.md
98
+ - VERIFY.sh
98
99
  - benchmark/baseline.json
99
100
  - benchmark/bench_agent_invoke.rb
100
101
  - benchmark/bench_context_assembler.rb
@@ -104,6 +105,22 @@ files:
104
105
  - benchmark/bench_vector_store.rb
105
106
  - benchmark/bench_workflow.rb
106
107
  - benchmark/run_all.rb
108
+ - docs/architecture.md
109
+ - docs/architecture/agent-context.md
110
+ - docs/architecture/before-llm-input.md
111
+ - docs/architecture/context-management.md
112
+ - docs/architecture/knowledge-and-rag.md
113
+ - docs/architecture/multi-agent-handoff.md
114
+ - docs/architecture/persistence.md
115
+ - docs/architecture/removed/agent-context.md
116
+ - docs/architecture/security-boundaries.md
117
+ - docs/architecture/tracing.md
118
+ - docs/archive/design/archived/04_api_design.md
119
+ - docs/archive/design/archived/09_guardrails.md
120
+ - docs/archive/design/archived/17_rails_integration.md
121
+ - docs/archive/design/historical/00_design_philosophy.md
122
+ - docs/archive/design/historical/01_rubyllm_evaluation.md
123
+ - docs/archive/design/historical/06_design_decisions.md
107
124
  - docs/changelog/0.14-and-earlier.md
108
125
  - docs/decisions/001-rubyllm-as-provider-layer.md
109
126
  - docs/decisions/002-workflow-context-immutability.md
@@ -121,22 +138,33 @@ files:
121
138
  - docs/decisions/013-journal-backed-knowledge-as-context-candidates.md
122
139
  - docs/decisions/014-unified-persistence-durable-state.md
123
140
  - docs/decisions/015-tool-public-facade-and-rbs-boundary.md
141
+ - docs/decisions/016-semantic-multi-agent-handoff.md
142
+ - docs/decisions/017-design-authority-and-adr-governance.md
143
+ - docs/decisions/018-durability-guarantees-and-failure-model.md
144
+ - docs/decisions/019-filter-contract-and-security-boundaries.md
145
+ - docs/decisions/020-canonical-workflow-instance-identity.md
146
+ - docs/decisions/021-generic-agent-invocation-identity-removal.md
147
+ - docs/decisions/022-agent-execution-parent-identity-and-runtime-routing-boundary.md
148
+ - docs/decisions/023-fsm-session-incarnation-identity-and-routing.md
149
+ - docs/decisions/024-event-loop-single-writer-agent-runtime.md
150
+ - docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md
151
+ - docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md
152
+ - docs/decisions/027-llm-adapter-provider-boundary.md
153
+ - docs/decisions/README.md
124
154
  - docs/features.md
125
155
  - docs/getting-started.md
126
156
  - docs/mcp-client.md
127
157
  - docs/migrations/0.15.md
128
158
  - docs/migrations/0.16.md
129
159
  - docs/migrations/0.19.md
160
+ - docs/migrations/0.22.md
130
161
  - docs/persistence-backends.md
131
162
  - docs/runtime-and-concurrency.md
132
- - examples/workflows/agent_event_mapping.rb
133
- - examples/workflows/generic_task_event_mapping.rb
163
+ - examples/README.md
134
164
  - gemfiles/mcp_1_0.gemfile
135
165
  - lib/phronomy.rb
136
166
  - lib/phronomy/agent.rb
137
- - lib/phronomy/agent/activation_registry.rb
138
167
  - lib/phronomy/agent/agent_execution.rb
139
- - lib/phronomy/agent/agent_execution_activation.rb
140
168
  - lib/phronomy/agent/agent_invocation.rb
141
169
  - lib/phronomy/agent/agent_invocation_session_builder.rb
142
170
  - lib/phronomy/agent/agent_root.rb
@@ -149,23 +177,15 @@ files:
149
177
  - lib/phronomy/agent/context/capability/base.rb
150
178
  - lib/phronomy/agent/context/instruction/prompt_template.rb
151
179
  - lib/phronomy/agent/context_assembler.rb
152
- - lib/phronomy/agent/context_candidate.rb
153
180
  - lib/phronomy/agent/context_candidate_resolver.rb
154
181
  - lib/phronomy/agent/context_importer.rb
155
- - lib/phronomy/agent/context_parts/budget/token_budget_packer.rb
156
- - lib/phronomy/agent/context_parts/requirements/required_context_resolver.rb
157
- - lib/phronomy/agent/context_parts/selectors/recent_first_selector.rb
158
- - lib/phronomy/agent/context_parts/unit_builders/dependency_aware_unit_builder.rb
159
182
  - lib/phronomy/agent/context_parts/validators/final_budget_validator.rb
160
183
  - lib/phronomy/agent/context_plan.rb
161
184
  - lib/phronomy/agent/context_plan_validator.rb
162
185
  - lib/phronomy/agent/context_policies/default.rb
163
186
  - lib/phronomy/agent/context_policy.rb
164
- - lib/phronomy/agent/context_policy_descriptor.rb
165
- - lib/phronomy/agent/context_policy_registry.rb
166
- - lib/phronomy/agent/context_request.rb
167
- - lib/phronomy/agent/context_selection_unit.rb
168
- - lib/phronomy/agent/derived_content_spec.rb
187
+ - lib/phronomy/agent/context_policy_input.rb
188
+ - lib/phronomy/agent/context_policy_input_builder.rb
169
189
  - lib/phronomy/agent/execution_coordinator.rb
170
190
  - lib/phronomy/agent/immutable.rb
171
191
  - lib/phronomy/agent/journal_projection.rb
@@ -177,8 +197,14 @@ files:
177
197
  - lib/phronomy/agent/llm_operation_result.rb
178
198
  - lib/phronomy/agent/phase_machine_builder.rb
179
199
  - lib/phronomy/agent/provider_call_outcome.rb
200
+ - lib/phronomy/agent/recovery_coordinator.rb
201
+ - lib/phronomy/agent/recovery_coordinator/continuation.rb
202
+ - lib/phronomy/agent/recovery_coordinator/installation.rb
203
+ - lib/phronomy/agent/recovery_coordinator/resolution.rb
204
+ - lib/phronomy/agent/recovery_support.rb
180
205
  - lib/phronomy/agent/ruby_llm_materializer.rb
181
- - lib/phronomy/agent/runner.rb
206
+ - lib/phronomy/agent/selection/candidate.rb
207
+ - lib/phronomy/agent/selection/constraint.rb
182
208
  - lib/phronomy/agent/shared_state.rb
183
209
  - lib/phronomy/agent/token_budget_resolver.rb
184
210
  - lib/phronomy/agent/tool_approval_request.rb
@@ -187,7 +213,9 @@ files:
187
213
  - lib/phronomy/agent/tool_executor.rb
188
214
  - lib/phronomy/agent/tool_invocation.rb
189
215
  - lib/phronomy/agent/tool_invocation_session_builder.rb
216
+ - lib/phronomy/agent_already_exists_error.rb
190
217
  - lib/phronomy/agent_busy_error.rb
218
+ - lib/phronomy/agent_purged_error.rb
191
219
  - lib/phronomy/canonical_json.rb
192
220
  - lib/phronomy/configuration.rb
193
221
  - lib/phronomy/content_store/base.rb
@@ -198,10 +226,12 @@ files:
198
226
  - lib/phronomy/engine/concurrency/cancellation_token.rb
199
227
  - lib/phronomy/engine/concurrency/deadline.rb
200
228
  - lib/phronomy/engine/concurrency/offload_pool.rb
229
+ - lib/phronomy/engine/concurrency/physical_completion_task.rb
201
230
  - lib/phronomy/engine/concurrency/pool_registry.rb
202
231
  - lib/phronomy/engine/event_loop.rb
203
232
  - lib/phronomy/engine/fsm_session.rb
204
233
  - lib/phronomy/engine/runtime.rb
234
+ - lib/phronomy/engine/runtime/agent_ownership_registry.rb
205
235
  - lib/phronomy/engine/runtime/shutdown_result.rb
206
236
  - lib/phronomy/engine/runtime/timer_queue.rb
207
237
  - lib/phronomy/engine/runtime/timer_service.rb
@@ -223,18 +253,33 @@ files:
223
253
  - lib/phronomy/llm_context_window/token_budget.rb
224
254
  - lib/phronomy/llm_context_window/token_estimator.rb
225
255
  - lib/phronomy/metrics.rb
256
+ - lib/phronomy/multi_agent/admission_registry.rb
257
+ - lib/phronomy/multi_agent/coordination_state.rb
258
+ - lib/phronomy/multi_agent/coordinator.rb
259
+ - lib/phronomy/multi_agent/execution_coordinator.rb
226
260
  - lib/phronomy/multi_agent/fan_out_invocation.rb
227
261
  - lib/phronomy/multi_agent/fan_out_session_builder.rb
228
262
  - lib/phronomy/multi_agent/handoff.rb
263
+ - lib/phronomy/multi_agent/handoff_capability_factory.rb
264
+ - lib/phronomy/multi_agent/handoff_context.rb
265
+ - lib/phronomy/multi_agent/handoff_policy.rb
266
+ - lib/phronomy/multi_agent/handoff_projection.rb
267
+ - lib/phronomy/multi_agent/handoff_request.rb
229
268
  - lib/phronomy/multi_agent/orchestrator.rb
230
269
  - lib/phronomy/multi_agent/parallel_tool_chat.rb
270
+ - lib/phronomy/multi_agent/runner.rb
231
271
  - lib/phronomy/multi_agent/team_coordinator.rb
232
272
  - lib/phronomy/output_parser.rb
233
273
  - lib/phronomy/output_parser/base.rb
234
274
  - lib/phronomy/output_parser/json_parser.rb
235
275
  - lib/phronomy/output_parser/structured_parser.rb
236
276
  - lib/phronomy/persistence.rb
277
+ - lib/phronomy/persistence/durable_codec.rb
278
+ - lib/phronomy/persistence/durable_record.rb
237
279
  - lib/phronomy/persistence/in_memory.rb
280
+ - lib/phronomy/persistence/migration/initial_format_migration.rb
281
+ - lib/phronomy/persistence/repository_facades.rb
282
+ - lib/phronomy/recovery.rb
238
283
  - lib/phronomy/ruby_llm_patches.rb
239
284
  - lib/phronomy/runnable.rb
240
285
  - lib/phronomy/stream_callback_error.rb
@@ -266,6 +311,7 @@ files:
266
311
  - lib/phronomy/tools/mcp.rb
267
312
  - lib/phronomy/tools/vector_search.rb
268
313
  - lib/phronomy/tracing.rb
314
+ - lib/phronomy/tracing/automatic.rb
269
315
  - lib/phronomy/tracing/base.rb
270
316
  - lib/phronomy/tracing/langfuse_tracer.rb
271
317
  - lib/phronomy/tracing/null_tracer.rb
@@ -289,6 +335,7 @@ files:
289
335
  - lib/phronomy/workflow.rb
290
336
  - lib/phronomy/workflow/phase_machine_builder.rb
291
337
  - lib/phronomy/workflow_context.rb
338
+ - lib/phronomy/workflow_recovery.rb
292
339
  - lib/phronomy/workflow_runner.rb
293
340
  - scripts/add_to_h_to_token_doubles.rb
294
341
  - scripts/add_to_h_unnamed_doubles.rb
@@ -304,6 +351,7 @@ files:
304
351
  - sig/phronomy/agent.rbs
305
352
  - sig/phronomy/extensions.rbs
306
353
  - sig/phronomy/llm_adapter.rbs
354
+ - sig/phronomy/multi_agent.rbs
307
355
  - sig/phronomy/persistence.rbs
308
356
  - sig/phronomy/runtime.rbs
309
357
  - sig/phronomy/tool.rbs
@@ -1,101 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "phronomy"
4
- require "securerandom"
5
-
6
- class GenerationContext
7
- include Phronomy::WorkflowContext
8
-
9
- field :prompt
10
- field :generation_request_id
11
- field :answer
12
- field :error_message
13
-
14
- # This is application logic. Phronomy transports the event and payload but
15
- # does not decide which Agent invocation is current or where the result lives.
16
- def handle_fsm_event(event)
17
- request_id = event.payload[:generation_request_id]
18
- return :consume unless request_id == generation_request_id
19
-
20
- case event.type
21
- when :generation_completed
22
- self.answer = event.payload[:agent_result][:output]
23
- when :generation_failed
24
- self.error_message = event.payload[:error].message
25
- end
26
- false
27
- end
28
- end
29
-
30
- class AnswerAgent < Phronomy::Agent::Base
31
- model "gpt-4o-mini"
32
- instructions "Answer clearly and briefly."
33
- end
34
-
35
- agent = AnswerAgent.new
36
- workflow = nil
37
-
38
- workflow = Phronomy::Workflow.define(GenerationContext) do
39
- initial :generating
40
-
41
- state :generating, action: ->(context) {
42
- request_id = context.generation_request_id
43
-
44
- # The Agent Task is intentionally not returned from the entry action.
45
- # The block is the application-level integration channel.
46
- agent.invoke_async(context.prompt) do |agent_event|
47
- workflow_event =
48
- case agent_event.type
49
- when :done
50
- :generation_completed
51
- when :error, :timeout, :cancelled, :approval_required
52
- :generation_failed
53
- end
54
- next unless workflow_event
55
-
56
- workflow.signal(
57
- thread_id: context.thread_id,
58
- event: workflow_event,
59
- payload: {
60
- generation_request_id: request_id,
61
- agent_result: (
62
- agent_event.payload if agent_event.type == :done
63
- ),
64
- error:
65
- agent_event.payload[:error] ||
66
- Phronomy::Error.new(
67
- "Agent requested Tool approval"
68
- )
69
- }
70
- )
71
- end
72
-
73
- context
74
- }
75
-
76
- state :succeeded
77
- state :failed
78
-
79
- transition(
80
- from: :generating,
81
- on: :generation_completed,
82
- to: :succeeded
83
- )
84
- transition(
85
- from: :generating,
86
- on: :generation_failed,
87
- to: :failed
88
- )
89
- transition from: :succeeded, to: :__finish__
90
- transition from: :failed, to: :__finish__
91
- end
92
-
93
- result = workflow.invoke(
94
- {
95
- prompt: "What is Run-to-Completion?",
96
- generation_request_id: SecureRandom.uuid
97
- },
98
- config: {thread_id: SecureRandom.uuid}
99
- )
100
-
101
- puts(result.answer || result.error_message)
@@ -1,66 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "phronomy"
4
-
5
- class ImportContext
6
- include Phronomy::WorkflowContext
7
-
8
- field :record_count, default: 0
9
- field :error_message
10
-
11
- def handle_fsm_event(event)
12
- case event.type
13
- when :import_completed
14
- self.record_count = event.payload[:record_count]
15
- when :import_failed
16
- self.error_message = event.payload[:error].message
17
- end
18
- false
19
- end
20
- end
21
-
22
- # Example application service for synchronous work that must stay off EventLoop.
23
- # The worker Thread belongs to Phronomy's bounded OffloadPool; the Workflow
24
- # itself never blocks EventLoop waiting for the operation.
25
- def start_import_async
26
- Phronomy::Runtime.instance.offload.submit(on_full: :raise) do
27
- # Replace with blocking file/DB/network work or another long synchronous call.
28
- 100
29
- end
30
- end
31
-
32
- workflow = nil
33
-
34
- workflow = Phronomy::Workflow.define(ImportContext) do
35
- initial :importing
36
-
37
- state :importing, action: ->(context) {
38
- operation = start_import_async
39
-
40
- operation.on_complete do |record_count, error|
41
- workflow.signal(
42
- thread_id: context.thread_id,
43
- event: error ? :import_failed : :import_completed,
44
- payload: {
45
- record_count: record_count,
46
- error: error
47
- }
48
- )
49
- end
50
-
51
- # Do not return the completion handle. The state remains active after this
52
- # synchronous action returns, and later completion arrives as an FSM event.
53
- context
54
- }
55
-
56
- state :completed
57
- state :failed
58
-
59
- transition from: :importing, on: :import_completed, to: :completed
60
- transition from: :importing, on: :import_failed, to: :failed
61
- transition from: :completed, to: :__finish__
62
- transition from: :failed, to: :__finish__
63
- end
64
-
65
- result = workflow.invoke({})
66
- puts "Imported #{result.record_count} records"
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module Agent
5
- class ActivationRegistry
6
- def initialize
7
- @mutex = Mutex.new
8
- @records = {}
9
- end
10
-
11
- def register(activation)
12
- @mutex.synchronize do
13
- raise ArgumentError, "activation already registered: #{activation.execution_id}" if @records.key?(activation.execution_id)
14
- @records[activation.execution_id] = activation
15
- end
16
- activation
17
- end
18
-
19
- def fetch(execution_id)
20
- @mutex.synchronize { @records[execution_id.to_s] }
21
- end
22
-
23
- def delete(execution_id)
24
- @mutex.synchronize { @records.delete(execution_id.to_s) }
25
- end
26
- end
27
- end
28
- end
@@ -1,172 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "securerandom"
4
- require "time"
5
-
6
- module Phronomy
7
- module Agent
8
- class AgentExecutionActivation
9
- attr_reader :execution_id, :agent, :application_listener, :coordinator,
10
- :base_manifest
11
- attr_accessor :invocation, :session
12
-
13
- def initialize(
14
- execution:,
15
- agent:,
16
- runtime_projection:,
17
- coordinator:,
18
- application_listener: nil
19
- )
20
- @execution_id = execution.execution_id
21
- @execution = execution
22
- @agent = agent
23
- @runtime_projection = runtime_projection
24
- @base_manifest = runtime_projection.manifest
25
- @coordinator = coordinator
26
- @application_listener = application_listener
27
- @mutex = Mutex.new
28
- @active_call = nil
29
- @llm_results = []
30
- @runtime_events = []
31
- @callback_errors = []
32
- end
33
-
34
- def execution
35
- @mutex.synchronize { @execution }
36
- end
37
-
38
- def replace_execution(value)
39
- @mutex.synchronize { @execution = value }
40
- end
41
-
42
- def runtime_projection
43
- @mutex.synchronize { @runtime_projection }
44
- end
45
-
46
- def replace_runtime_projection(value)
47
- @mutex.synchronize { @runtime_projection = value }
48
- end
49
-
50
- # Allocates Provider Call identity before transport begins. This identity is
51
- # provenance only; Context selection must not use it as a semantic boundary.
52
- def begin_llm_call(projection)
53
- call_context = {
54
- llm_call_id: SecureRandom.uuid,
55
- manifest_ref: projection.manifest_ref,
56
- started_at: Time.now.utc.iso8601(6)
57
- }.freeze
58
- @mutex.synchronize do
59
- if @active_call
60
- raise Phronomy::Error,
61
- "cannot start a Provider Call while another Provider Call is active"
62
- end
63
- @runtime_projection = projection
64
- @active_call = call_context
65
- end
66
- call_context
67
- end
68
-
69
- def record_llm_result(response:, error:, streaming:)
70
- @mutex.synchronize do
71
- active_call = @active_call
72
- unless active_call
73
- raise Phronomy::Error, "LLM result arrived without an active Provider Call"
74
- end
75
- @llm_results << {
76
- llm_call_id: active_call.fetch(:llm_call_id),
77
- response: response,
78
- error: error,
79
- streaming: streaming,
80
- manifest_ref: active_call.fetch(:manifest_ref),
81
- started_at: active_call.fetch(:started_at)
82
- }
83
- @active_call = nil
84
- end
85
- end
86
-
87
- def runtime_snapshot
88
- @mutex.synchronize do
89
- {
90
- llm_results: @llm_results.dup,
91
- runtime_events: @runtime_events.dup,
92
- active_call: @active_call&.dup
93
- }
94
- end
95
- end
96
-
97
- def acknowledge_runtime_snapshot(snapshot)
98
- @mutex.synchronize do
99
- @llm_results.shift(snapshot.fetch(:llm_results).length)
100
- @runtime_events.shift(snapshot.fetch(:runtime_events).length)
101
- if snapshot[:active_call] && @active_call == snapshot[:active_call]
102
- @active_call = nil
103
- end
104
- end
105
- end
106
-
107
- ApplicationCallbackFailure = Data.define(:event_type, :error) do
108
- def to_stream_callback_error
109
- wrapped = Phronomy::StreamCallbackError.new(
110
- event_type: event_type, original_error: error, result: nil
111
- )
112
- begin
113
- raise wrapped, cause: error
114
- rescue Phronomy::StreamCallbackError => caught
115
- caught.set_backtrace(error.backtrace)
116
- caught
117
- end
118
- end
119
- end
120
-
121
- attr_reader :callback_failure
122
-
123
- def callback_failed?
124
- @mutex.synchronize { !@callback_failure.nil? }
125
- end
126
-
127
- # Canonical runtime recording is independent of Application callback health.
128
- # Once an event is observed it is appended even after a listener has failed.
129
- def record_event(event)
130
- listener = @mutex.synchronize do
131
- @runtime_events << event
132
- @callback_failure ? nil : @application_listener
133
- end
134
- return unless listener
135
-
136
- listener.call(event)
137
- rescue => callback_error
138
- failure = ApplicationCallbackFailure.new(
139
- event_type: event.type, error: callback_error
140
- )
141
- @mutex.synchronize do
142
- @callback_failure ||= failure
143
- @application_listener = nil
144
- end
145
- notify_callback_failure(failure)
146
- end
147
-
148
- private
149
-
150
- def notify_callback_failure(failure)
151
- invocation = @mutex.synchronize { @invocation }
152
- if invocation
153
- accepted = Phronomy::Runtime.instance.event_loop.post_to_session(
154
- Phronomy::Event.new(
155
- type: :application_callback_failed,
156
- target_id: invocation.id,
157
- payload: {failure: failure}
158
- )
159
- )
160
- unless accepted
161
- Phronomy.configuration.logger&.warn(
162
- "[Phronomy] Callback failure recorded but could not notify FSM: execution_id=#{@execution_id}"
163
- )
164
- end
165
- end
166
- Phronomy.configuration.logger&.warn(
167
- "[Phronomy] Application event listener failed: #{failure.error.class}: #{failure.error.message}"
168
- )
169
- end
170
- end
171
- end
172
- end
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module Agent
5
- ContextCandidate = Data.define(
6
- :candidate_id,
7
- :source_kind,
8
- :category,
9
- :role,
10
- :content_ref,
11
- :record_id,
12
- :agent_id,
13
- :execution_id,
14
- :llm_call_id,
15
- :tool_call_id,
16
- :sequence,
17
- :requirement,
18
- :priority,
19
- :metadata
20
- ) do
21
- def initialize(**values)
22
- normalized = values.merge(
23
- candidate_id: values.fetch(:candidate_id).to_s.freeze,
24
- source_kind: values.fetch(:source_kind).to_sym,
25
- category: values.fetch(:category).to_sym,
26
- role: values[:role]&.to_sym,
27
- content_ref: values.fetch(:content_ref).to_s.freeze,
28
- record_id: values[:record_id]&.to_s&.freeze,
29
- agent_id: values[:agent_id]&.to_s&.freeze,
30
- execution_id: values[:execution_id]&.to_s&.freeze,
31
- llm_call_id: values[:llm_call_id]&.to_s&.freeze,
32
- tool_call_id: values[:tool_call_id]&.to_s&.freeze,
33
- sequence: values[:sequence] && Integer(values[:sequence]),
34
- requirement: (values[:requirement] || :optional).to_sym,
35
- priority: Integer(values[:priority] || 0),
36
- metadata: Immutable.copy(values[:metadata] || {})
37
- )
38
- unless %i[protocol_required declared_required optional].include?(normalized[:requirement])
39
- raise ArgumentError, "unknown ContextCandidate requirement: #{normalized[:requirement].inspect}"
40
- end
41
-
42
- super(**normalized)
43
- freeze
44
- end
45
- end
46
- end
47
- end
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phronomy
4
- module Agent
5
- module ContextParts
6
- module Budget
7
- class TokenBudgetPacker
8
- def pack(request:, units:)
9
- return Array(units).freeze unless request.token_budget
10
-
11
- candidate_index = request.candidates.to_h { |candidate| [candidate.candidate_id, candidate] }
12
- mandatory = Integer(request.metadata["mandatory_token_estimate"] || 0)
13
- limit = request.token_budget.effective_input_limit
14
- if mandatory > limit
15
- raise Phronomy::ContextBudgetExceededError,
16
- "Mandatory content (estimated #{mandatory} tokens) exceeds " \
17
- "available input budget (#{limit} tokens)"
18
- end
19
-
20
- remaining = limit - mandatory
21
- required, optional = Array(units).partition { |unit| unit.requirement != :optional }
22
- required_cost = required.sum { |unit| unit_cost(unit, candidate_index) }
23
- if required_cost > remaining
24
- raise Phronomy::ContextBudgetExceededError,
25
- "Required Context (estimated #{required_cost} tokens) exceeds " \
26
- "remaining input budget (#{remaining} tokens after mandatory content)"
27
- end
28
-
29
- selected = required.dup
30
- remaining -= required_cost
31
- optional.each do |unit|
32
- cost = unit_cost(unit, candidate_index)
33
- next if cost > remaining
34
-
35
- selected << unit
36
- remaining -= cost
37
- end
38
- selected.freeze
39
- end
40
-
41
- private
42
-
43
- def unit_cost(unit, candidate_index)
44
- unit.candidate_ids.sum do |candidate_id|
45
- candidate = candidate_index.fetch(candidate_id)
46
- Integer(candidate.metadata["estimated_tokens"] || 0)
47
- end
48
- end
49
- end
50
- end
51
- end
52
- end
53
- end