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
@@ -0,0 +1,211 @@
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(
41
+ :execution, :root, :continuation, :failure, :appended_records
42
+ )
43
+ RecoveryPlan = Data.define(
44
+ :execution, :root, :manifest, :base_manifest,
45
+ :projection, :classification
46
+ )
47
+
48
+ attr_reader :agent
49
+
50
+ def initialize(agent)
51
+ @agent = agent
52
+ end
53
+
54
+ def recover_on_load!
55
+ active = agent.persistence.transaction do |tx|
56
+ Array(tx.executions.list_active(agent.agent_id))
57
+ end
58
+ if active.length > 1
59
+ raise Phronomy::Persistence::ConflictError,
60
+ "multiple active AgentExecutions exist for #{agent.agent_id}"
61
+ end
62
+ return agent if active.empty?
63
+
64
+ execution = active.first
65
+ unless execution.agent_id.to_s == agent.agent_id.to_s
66
+ raise Phronomy::Persistence::ConflictError,
67
+ "active AgentExecution belongs to another Agent: #{execution.agent_id}"
68
+ end
69
+
70
+ plan = prepare_plan(execution)
71
+ classification = plan.classification
72
+ if classification.disposition ==
73
+ Phronomy::Recovery::RESOLUTION_REQUIRED &&
74
+ !agent.send(:_phronomy_event_listener)
75
+ raise Phronomy::ConfigurationError,
76
+ "Agent #{agent.agent_id.inspect} requires Recovery resolution; " \
77
+ "load must register on_event"
78
+ end
79
+ if execution.status == :suspended &&
80
+ !agent.send(:_phronomy_event_listener)
81
+ raise Phronomy::ConfigurationError,
82
+ "Agent #{agent.agent_id.inspect} has a pending approval; " \
83
+ "load must register on_event"
84
+ end
85
+
86
+ completion = Phronomy::Task.deferred(
87
+ name: "agent-recovery-load:#{execution.execution_id}"
88
+ )
89
+ command = InstallCommand.new(
90
+ coordinator: self,
91
+ plan: plan,
92
+ completion: completion,
93
+ owner_token: Object.new.freeze
94
+ )
95
+ unless post_control(command)
96
+ completion.fail(
97
+ Phronomy::RuntimeShutdownError.new(
98
+ "EventLoop rejected Agent Recovery installation"
99
+ )
100
+ )
101
+ end
102
+ completion.wait_result
103
+ agent
104
+ end
105
+
106
+ def resolve(
107
+ execution_id,
108
+ expected_execution_revision:,
109
+ subject:,
110
+ outcome:,
111
+ result: Phronomy::Recovery::MISSING,
112
+ error: Phronomy::Recovery::MISSING
113
+ )
114
+ agent.send(:__assert_live_agent!)
115
+ normalized_outcome = Phronomy::Recovery.normalize_outcome(outcome)
116
+ result_present = !result.equal?(Phronomy::Recovery::MISSING)
117
+ error_present = !error.equal?(Phronomy::Recovery::MISSING)
118
+ Phronomy::Recovery.validate_resolution_material!(
119
+ outcome: normalized_outcome,
120
+ result_present: result_present,
121
+ error_present: error_present
122
+ )
123
+
124
+ normalized_subject =
125
+ Phronomy::Recovery.normalize_subject(subject)
126
+ canonical_result = if result_present
127
+ if normalized_subject[:type] == :llm_call &&
128
+ normalized_outcome == :succeeded
129
+ RecoverySupport.normalize_provider_outcome(result).to_h
130
+ else
131
+ RecoverySupport.canonical_copy(result)
132
+ end
133
+ end
134
+ failure = error_present ?
135
+ RecoverySupport.resolution_failure(error) : nil
136
+
137
+ completion = Phronomy::Task.deferred(
138
+ name: "agent-recovery-resolve:#{execution_id}"
139
+ )
140
+ command = ResolveCommand.new(
141
+ coordinator: self,
142
+ execution_id: execution_id.to_s.freeze,
143
+ expected_execution_revision:
144
+ Integer(expected_execution_revision),
145
+ subject: normalized_subject,
146
+ outcome: normalized_outcome,
147
+ result: canonical_result,
148
+ failure: failure,
149
+ completion: completion
150
+ )
151
+ unless post_control(command)
152
+ completion.fail(
153
+ Phronomy::RuntimeShutdownError.new(
154
+ "EventLoop rejected Agent Recovery resolution"
155
+ )
156
+ )
157
+ end
158
+ completion
159
+ rescue => caught
160
+ completion ||= Phronomy::Task.deferred(
161
+ name: "agent-recovery-resolve:#{execution_id}"
162
+ )
163
+ completion.fail(caught)
164
+ completion
165
+ end
166
+
167
+ def deliver_on_event_loop(command)
168
+ case command
169
+ when InstallCommand
170
+ install_on_event_loop(command)
171
+ when ResolveCommand
172
+ begin_resolve_on_event_loop(command)
173
+ when ResolveReady
174
+ apply_resolve_on_event_loop(command)
175
+ else
176
+ raise Phronomy::Error,
177
+ "unknown Recovery control command: #{command.class}"
178
+ end
179
+ end
180
+
181
+ private
182
+
183
+ def post_control(command)
184
+ Phronomy::Runtime.instance.event_loop.post(
185
+ Phronomy::Event.new(
186
+ type: :agent_control,
187
+ target_id:
188
+ Phronomy::EventLoop::SYSTEM_CHANNEL_ID,
189
+ payload: {command: command}.freeze
190
+ )
191
+ )
192
+ rescue Phronomy::RuntimeShutdownError
193
+ false
194
+ end
195
+ end
196
+ end
197
+ end
198
+
199
+ require_relative "recovery_coordinator/installation"
200
+ require_relative "recovery_coordinator/resolution"
201
+ require_relative "recovery_coordinator/continuation"
202
+
203
+ module Phronomy
204
+ module Agent
205
+ class RecoveryCoordinator
206
+ include Installation
207
+ include Resolution
208
+ include Continuation
209
+ end
210
+ end
211
+ end