phronomy 0.22.0 → 0.24.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 (178) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +3 -4
  3. data/CHANGELOG.md +215 -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 +147 -0
  15. data/docs/architecture/persistence.md +186 -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 +89 -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 +114 -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/028-preparing-recovery-replay-contract.md +106 -0
  45. data/docs/decisions/029-semantic-completion-and-application-effect-boundary.md +220 -0
  46. data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +235 -0
  47. data/docs/decisions/031-durable-multi-agent-coordination.md +301 -0
  48. data/docs/decisions/README.md +176 -0
  49. data/docs/design/durable-semantic-coordination/CHANGELOG_V2_REVISION_2.md +33 -0
  50. data/docs/design/durable-semantic-coordination/CONTINUATION_DECISION_REFACTOR.md +191 -0
  51. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +862 -0
  52. data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +106 -0
  53. data/docs/design/durable-semantic-coordination/RECOVERY_CONTRACT_CLARIFICATIONS.md +179 -0
  54. data/docs/design/durable-semantic-coordination/RESPONSIBILITY_BOUNDARY_REVIEW.md +302 -0
  55. data/docs/features.md +65 -11
  56. data/docs/getting-started.md +77 -45
  57. data/docs/migrations/0.19.md +14 -7
  58. data/docs/migrations/0.22.md +390 -0
  59. data/docs/migrations/durable-semantic-coordination-v2.md +65 -0
  60. data/docs/persistence-backends.md +129 -40
  61. data/docs/runtime-and-concurrency.md +227 -33
  62. data/examples/README.md +13 -0
  63. data/lib/phronomy/agent/agent_execution.rb +21 -17
  64. data/lib/phronomy/agent/agent_invocation.rb +288 -93
  65. data/lib/phronomy/agent/agent_invocation_session_builder.rb +236 -202
  66. data/lib/phronomy/agent/agent_root.rb +3 -3
  67. data/lib/phronomy/agent/approval_evaluation_request.rb +37 -19
  68. data/lib/phronomy/agent/async_event_api.rb +162 -72
  69. data/lib/phronomy/agent/base.rb +416 -181
  70. data/lib/phronomy/agent/concerns/before_llm_input.rb +1 -1
  71. data/lib/phronomy/agent/context_assembler.rb +437 -178
  72. data/lib/phronomy/agent/context_candidate_resolver.rb +2 -2
  73. data/lib/phronomy/agent/context_plan.rb +18 -13
  74. data/lib/phronomy/agent/context_plan_validator.rb +246 -88
  75. data/lib/phronomy/agent/context_policies/default.rb +123 -34
  76. data/lib/phronomy/agent/context_policy.rb +109 -3
  77. data/lib/phronomy/agent/context_policy_input.rb +244 -0
  78. data/lib/phronomy/agent/context_policy_input_builder.rb +241 -0
  79. data/lib/phronomy/agent/exact_execution.rb +153 -0
  80. data/lib/phronomy/agent/execution_cancellation.rb +25 -0
  81. data/lib/phronomy/agent/execution_coordinator.rb +2417 -572
  82. data/lib/phronomy/agent/handoff.rb +43 -0
  83. data/lib/phronomy/agent/handoff_capability_factory.rb +45 -0
  84. data/lib/phronomy/agent/handoff_context.rb +120 -0
  85. data/lib/phronomy/agent/handoff_execution_coordinator.rb +143 -0
  86. data/lib/phronomy/agent/handoff_policy.rb +143 -0
  87. data/lib/phronomy/agent/handoff_projection.rb +207 -0
  88. data/lib/phronomy/agent/handoff_request.rb +45 -0
  89. data/lib/phronomy/agent/handoff_runner.rb +178 -0
  90. data/lib/phronomy/agent/handoff_state.rb +43 -0
  91. data/lib/phronomy/agent/journal_record.rb +17 -4
  92. data/lib/phronomy/agent/llm_input_build_context.rb +1 -1
  93. data/lib/phronomy/agent/llm_input_manifest.rb +277 -2
  94. data/lib/phronomy/agent/llm_operation_result.rb +12 -7
  95. data/lib/phronomy/agent/phase_machine_builder.rb +19 -7
  96. data/lib/phronomy/agent/provider_call_outcome.rb +23 -7
  97. data/lib/phronomy/agent/recovery_coordinator/continuation.rb +174 -0
  98. data/lib/phronomy/agent/recovery_coordinator/installation.rb +381 -0
  99. data/lib/phronomy/agent/recovery_coordinator/resolution.rb +511 -0
  100. data/lib/phronomy/agent/recovery_coordinator.rb +217 -0
  101. data/lib/phronomy/agent/recovery_support.rb +504 -0
  102. data/lib/phronomy/agent/ruby_llm_materializer.rb +16 -13
  103. data/lib/phronomy/agent/selection/candidate.rb +53 -0
  104. data/lib/phronomy/agent/selection/constraint.rb +49 -0
  105. data/lib/phronomy/agent/shared_state.rb +38 -1
  106. data/lib/phronomy/agent/tool_approval_request.rb +33 -5
  107. data/lib/phronomy/agent/tool_definition_set.rb +49 -3
  108. data/lib/phronomy/agent/tool_invocation.rb +339 -103
  109. data/lib/phronomy/agent/tool_invocation_session_builder.rb +49 -45
  110. data/lib/phronomy/agent.rb +20 -2
  111. data/lib/phronomy/agent_already_exists_error.rb +5 -0
  112. data/lib/phronomy/agent_purged_error.rb +5 -0
  113. data/lib/phronomy/engine/concurrency/offload_pool.rb +17 -3
  114. data/lib/phronomy/engine/concurrency/physical_completion_task.rb +135 -0
  115. data/lib/phronomy/engine/event_loop.rb +622 -63
  116. data/lib/phronomy/engine/fsm_session.rb +194 -21
  117. data/lib/phronomy/engine/runtime/agent_ownership_registry.rb +352 -0
  118. data/lib/phronomy/engine/runtime/team_ownership_registry.rb +77 -0
  119. data/lib/phronomy/engine/runtime.rb +92 -20
  120. data/lib/phronomy/generator_verifier.rb +12 -14
  121. data/lib/phronomy/invocation_context.rb +9 -29
  122. data/lib/phronomy/multi_agent/admission_registry.rb +51 -0
  123. data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +134 -0
  124. data/lib/phronomy/multi_agent/fan_out_invocation.rb +24 -33
  125. data/lib/phronomy/multi_agent/fan_out_session_builder.rb +12 -19
  126. data/lib/phronomy/multi_agent/orchestrator.rb +71 -26
  127. data/lib/phronomy/multi_agent/team_coordinator.rb +473 -125
  128. data/lib/phronomy/multi_agent/team_execution.rb +44 -0
  129. data/lib/phronomy/multi_agent/team_root.rb +41 -0
  130. data/lib/phronomy/persistence/durable_codec.rb +706 -0
  131. data/lib/phronomy/persistence/durable_record.rb +117 -0
  132. data/lib/phronomy/persistence/in_memory.rb +471 -133
  133. data/lib/phronomy/persistence/migration/initial_format_migration.rb +226 -0
  134. data/lib/phronomy/persistence/repository_facades.rb +535 -0
  135. data/lib/phronomy/persistence.rb +174 -40
  136. data/lib/phronomy/recovery.rb +186 -0
  137. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +2 -2
  138. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +2 -1
  139. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +19 -19
  140. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +3 -3
  141. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +5 -5
  142. data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +137 -0
  143. data/lib/phronomy/testing/persistence_contract.rb +5 -0
  144. data/lib/phronomy/tools/agent.rb +1 -1
  145. data/lib/phronomy/tracing/automatic.rb +176 -0
  146. data/lib/phronomy/tracing/base.rb +11 -2
  147. data/lib/phronomy/tracing/langfuse_tracer.rb +20 -12
  148. data/lib/phronomy/version.rb +1 -1
  149. data/lib/phronomy/workflow.rb +3 -6
  150. data/lib/phronomy/workflow_context.rb +14 -5
  151. data/lib/phronomy/workflow_recovery.rb +123 -0
  152. data/lib/phronomy/workflow_runner.rb +468 -256
  153. data/lib/phronomy.rb +6 -0
  154. data/scripts/api_snapshot.rb +12 -0
  155. data/sig/phronomy/agent.rbs +209 -7
  156. data/sig/phronomy/handoff.rbs +41 -0
  157. data/sig/phronomy/multi_agent.rbs +35 -0
  158. data/sig/phronomy/persistence.rbs +123 -4
  159. data/sig/phronomy/runtime.rbs +1 -4
  160. data/sig/phronomy/workflow.rbs +2 -2
  161. data/sig/phronomy.rbs +10 -0
  162. metadata +84 -18
  163. data/examples/workflows/agent_event_mapping.rb +0 -101
  164. data/examples/workflows/generic_task_event_mapping.rb +0 -66
  165. data/lib/phronomy/agent/activation_registry.rb +0 -28
  166. data/lib/phronomy/agent/agent_execution_activation.rb +0 -172
  167. data/lib/phronomy/agent/context_candidate.rb +0 -47
  168. data/lib/phronomy/agent/context_parts/budget/token_budget_packer.rb +0 -53
  169. data/lib/phronomy/agent/context_parts/requirements/required_context_resolver.rb +0 -56
  170. data/lib/phronomy/agent/context_parts/selectors/recent_first_selector.rb +0 -30
  171. data/lib/phronomy/agent/context_parts/unit_builders/dependency_aware_unit_builder.rb +0 -118
  172. data/lib/phronomy/agent/context_policy_descriptor.rb +0 -49
  173. data/lib/phronomy/agent/context_policy_registry.rb +0 -46
  174. data/lib/phronomy/agent/context_request.rb +0 -35
  175. data/lib/phronomy/agent/context_selection_unit.rb +0 -38
  176. data/lib/phronomy/agent/derived_content_spec.rb +0 -34
  177. data/lib/phronomy/agent/runner.rb +0 -97
  178. data/lib/phronomy/multi_agent/handoff.rb +0 -64
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "recovery_support"
4
+
5
+ module Phronomy
6
+ module Agent
7
+ # Durable Agent Recovery owner/resolver.
8
+ #
9
+ # This class owns Agent-specific restart hydration and factual resolution.
10
+ # Shared Recovery semantics remain in Phronomy::Recovery.
11
+ # @api private
12
+ class RecoveryCoordinator
13
+ class ResolutionOutcomeUnknownError < Phronomy::Error
14
+ attr_reader :original_error, :intended_result
15
+
16
+ def initialize(original_error, intended_result)
17
+ @original_error = original_error
18
+ @intended_result = intended_result
19
+ super(
20
+ "Recovery resolution Persistence outcome is unknown: " + "#{original_error.class}: #{original_error.message}"
21
+ )
22
+ set_backtrace(original_error.backtrace)
23
+ end
24
+ end
25
+ private_constant :ResolutionOutcomeUnknownError
26
+
27
+ InstallCommand = Data.define(
28
+ :coordinator, :plan, :completion, :owner_token
29
+ )
30
+ ResolveCommand = Data.define(
31
+ :coordinator, :execution_id, :expected_execution_revision,
32
+ :subject, :outcome, :result, :failure, :completion
33
+ )
34
+ ResolveReady = Data.define(
35
+ :coordinator, :request, :operation, :result, :error
36
+ )
37
+ ResolutionOperation = Data.define(
38
+ :execution, :root, :subject, :outcome, :result, :failure
39
+ )
40
+ ResolutionResult = Data.define(:execution)
41
+ ResolutionPreparation = Data.define(:execution, :material, :error)
42
+ RecoveryMaterial = Data.define(:projection, :messages, :assistant_message, :output, :usage)
43
+ RecoveryPlan = Data.define(
44
+ :execution, :root, :manifest, :base_manifest,
45
+ :projection, :classification, :material
46
+ )
47
+
48
+ attr_reader :agent
49
+
50
+ def initialize(agent)
51
+ @agent = agent
52
+ @runtime = Phronomy::Runtime.instance
53
+ end
54
+
55
+ def recover_on_load!
56
+ active = agent.persistence.transaction do |tx|
57
+ Array(tx.executions.list_active(agent.agent_id))
58
+ end
59
+ if active.length > 1
60
+ raise Phronomy::Persistence::ConflictError,
61
+ "multiple active AgentExecutions exist for #{agent.agent_id}"
62
+ end
63
+ return agent if active.empty?
64
+
65
+ execution = active.first
66
+ unless execution.agent_id.to_s == agent.agent_id.to_s
67
+ raise Phronomy::Persistence::ConflictError,
68
+ "active AgentExecution belongs to another Agent: #{execution.agent_id}"
69
+ end
70
+
71
+ coordination = execution.metadata["coordination"]
72
+ if coordination && agent.__coordination_config.empty?
73
+ return agent
74
+ end
75
+
76
+ plan = prepare_plan(execution)
77
+ classification = plan.classification
78
+ if classification.disposition ==
79
+ Phronomy::Recovery::RESOLUTION_REQUIRED &&
80
+ !agent.send(:_phronomy_event_listener)
81
+ raise Phronomy::ConfigurationError,
82
+ "Agent #{agent.agent_id.inspect} requires Recovery resolution; " \
83
+ "load must register on_event"
84
+ end
85
+ if execution.status == :suspended &&
86
+ !agent.send(:_phronomy_event_listener)
87
+ raise Phronomy::ConfigurationError,
88
+ "Agent #{agent.agent_id.inspect} has a pending approval; " \
89
+ "load must register on_event"
90
+ end
91
+
92
+ completion = Phronomy::Task.deferred(
93
+ name: "agent-recovery-load:#{execution.execution_id}"
94
+ )
95
+ command = InstallCommand.new(
96
+ coordinator: self,
97
+ plan: plan,
98
+ completion: completion,
99
+ owner_token: Object.new.freeze
100
+ )
101
+ unless post_control(command)
102
+ completion.fail(
103
+ Phronomy::RuntimeShutdownError.new(
104
+ "EventLoop rejected Agent Recovery installation"
105
+ )
106
+ )
107
+ end
108
+ completion.wait_result
109
+ agent
110
+ end
111
+
112
+ def resolve(
113
+ execution_id,
114
+ expected_execution_revision:,
115
+ subject:,
116
+ outcome:,
117
+ result: Phronomy::Recovery::MISSING,
118
+ error: Phronomy::Recovery::MISSING
119
+ )
120
+ agent.send(:__assert_live_agent!)
121
+ normalized_outcome = Phronomy::Recovery.normalize_outcome(outcome)
122
+ result_present = !result.equal?(Phronomy::Recovery::MISSING)
123
+ error_present = !error.equal?(Phronomy::Recovery::MISSING)
124
+ Phronomy::Recovery.validate_resolution_material!(
125
+ outcome: normalized_outcome,
126
+ result_present: result_present,
127
+ error_present: error_present
128
+ )
129
+
130
+ normalized_subject =
131
+ Phronomy::Recovery.normalize_subject(subject)
132
+ canonical_result = if result_present
133
+ if normalized_subject[:type] == :llm_call &&
134
+ normalized_outcome == :succeeded
135
+ RecoverySupport.normalize_provider_outcome(result).to_h
136
+ else
137
+ RecoverySupport.canonical_copy(result)
138
+ end
139
+ end
140
+ failure = error_present ?
141
+ RecoverySupport.resolution_failure(error) : nil
142
+
143
+ completion = Phronomy::Task.deferred(
144
+ name: "agent-recovery-resolve:#{execution_id}"
145
+ )
146
+ command = ResolveCommand.new(
147
+ coordinator: self,
148
+ execution_id: execution_id.to_s.freeze,
149
+ expected_execution_revision:
150
+ Integer(expected_execution_revision),
151
+ subject: normalized_subject,
152
+ outcome: normalized_outcome,
153
+ result: canonical_result,
154
+ failure: failure,
155
+ completion: completion
156
+ )
157
+ unless post_control(command)
158
+ completion.fail(
159
+ Phronomy::RuntimeShutdownError.new(
160
+ "EventLoop rejected Agent Recovery resolution"
161
+ )
162
+ )
163
+ end
164
+ completion
165
+ rescue => caught
166
+ completion ||= Phronomy::Task.deferred(
167
+ name: "agent-recovery-resolve:#{execution_id}"
168
+ )
169
+ completion.fail(caught)
170
+ completion
171
+ end
172
+
173
+ def deliver_on_event_loop(command)
174
+ case command
175
+ when InstallCommand
176
+ install_on_event_loop(command)
177
+ when ResolveCommand
178
+ begin_resolve_on_event_loop(command)
179
+ when ResolveReady
180
+ apply_resolve_on_event_loop(command)
181
+ else
182
+ raise Phronomy::Error,
183
+ "unknown Recovery control command: #{command.class}"
184
+ end
185
+ end
186
+
187
+ private
188
+
189
+ def post_control(command)
190
+ @runtime.event_loop.post(
191
+ Phronomy::Event.new(
192
+ type: :agent_control,
193
+ target_id:
194
+ Phronomy::EventLoop::SYSTEM_CHANNEL_ID,
195
+ payload: {command: command}.freeze
196
+ )
197
+ )
198
+ rescue Phronomy::RuntimeShutdownError
199
+ false
200
+ end
201
+ end
202
+ end
203
+ end
204
+
205
+ require_relative "recovery_coordinator/installation"
206
+ require_relative "recovery_coordinator/resolution"
207
+ require_relative "recovery_coordinator/continuation"
208
+
209
+ module Phronomy
210
+ module Agent
211
+ class RecoveryCoordinator
212
+ include Installation
213
+ include Resolution
214
+ include Continuation
215
+ end
216
+ end
217
+ end