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,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Agent
5
+ # Observes a reserved execution using ordinary Agent admission and Recovery.
6
+ # Durable reads/materialization run off EventLoop. Workers never wait for a
7
+ # child lifecycle: the existing EventLoop completion waiter settles Task.
8
+ # @api private
9
+ class ExactExecution
10
+ Wait = Data.define(:coordinator, :agent, :execution_id)
11
+ private_constant :Wait
12
+
13
+ def self.start(agent:, execution_id:, input:, config: {})
14
+ new(agent, execution_id, input, config).start
15
+ end
16
+
17
+ def initialize(agent, execution_id, input, config)
18
+ @agent, @id, @input, @config = agent, execution_id.to_s.freeze, input, config.freeze
19
+ @runtime = Phronomy::Runtime.instance
20
+ @completion = Phronomy::Task.deferred(name: "exact-execution:#{@id}")
21
+ end
22
+
23
+ def start
24
+ preparation = @runtime.offload.submit(on_full: :raise) do
25
+ execution = read_execution
26
+ if execution&.terminal?
27
+ [:terminal, materialize(execution)]
28
+ elsif execution
29
+ unless @runtime.__agent_execution_owner(@id)
30
+ @agent.instance_variable_set(:@_phronomy_coordination_config, @config)
31
+ begin
32
+ RecoveryCoordinator.new(@agent).recover_on_load!
33
+ rescue Phronomy::ConfigurationError => error
34
+ raise Phronomy::ExecutionRehydrationRequiredError,
35
+ "Execution #{@id} needs current recovery wiring: #{error.message}"
36
+ end
37
+ end
38
+ [:active, nil]
39
+ else
40
+ @config[:cancellation_token]&.raise_if_cancelled!
41
+ [:absent, nil]
42
+ end
43
+ end
44
+ preparation.on_complete do |prepared, error|
45
+ if error
46
+ @completion.fail(error)
47
+ else
48
+ disposition, result = prepared
49
+ case disposition
50
+ when :terminal then @completion.complete(result)
51
+ when :absent
52
+ source = @agent.send(:_start_agent_operation, @input,
53
+ config: @config.merge(phronomy_reserved_execution_id: @id, phronomy_exact_observers: [@completion].freeze),
54
+ mode: :invoke, listener: @agent.send(:_phronomy_event_listener))
55
+ source.on_complete { |_value, failure| reconcile(failure) }
56
+ when :active
57
+ command = Wait.new(coordinator: self, agent: @agent, execution_id: @id)
58
+ posted = @runtime.event_loop.post(Phronomy::Event.new(type: :agent_terminal_ready,
59
+ target_id: Phronomy::EventLoop::SYSTEM_CHANNEL_ID, payload: {command: command}))
60
+ @completion.fail(Phronomy::RuntimeShutdownError.new("Exact execution observer rejected")) unless posted
61
+ end
62
+ end
63
+ rescue => failure
64
+ @completion.fail(failure)
65
+ end
66
+ @completion
67
+ rescue => failure
68
+ @completion.fail(failure)
69
+ @completion
70
+ end
71
+
72
+ def deliver_on_event_loop(command)
73
+ state = @runtime.event_loop.agent_execution_state(command.execution_id)
74
+ unless state
75
+ # Ownership can be released between the durable read and this command.
76
+ # Reinstall once with the caller's current wiring/cancellation request.
77
+ if !@reinstall_attempted
78
+ @reinstall_attempted = true
79
+ return start
80
+ end
81
+ return reconcile(nil)
82
+ end
83
+ unless state.agent.equal?(command.agent)
84
+ raise Phronomy::Persistence::ConflictError, "Exact execution #{@id} owner mismatch"
85
+ end
86
+ if @config[:cancellation_token]&.cancelled?
87
+ state.invocation&.config&.fetch(:cancellation_token, nil)&.cancel!
88
+ end
89
+ if state.execution.status == :suspended || state.fsm_session_id.nil?
90
+ raise Phronomy::ExecutionRehydrationRequiredError,
91
+ "Execution #{@id} requires approval, factual resolution or current recovery wiring"
92
+ end
93
+ observers = Array(state.invocation.config[:phronomy_exact_observers])
94
+ state.invocation.merge_config!(phronomy_exact_observers: (observers + [@completion]).uniq.freeze)
95
+ waiter = Phronomy::Task.deferred(name: "exact-wait:#{@id}")
96
+ waiter.on_complete { |_result, failure| reconcile(failure) }
97
+ @runtime.event_loop.register_agent_completion_waiter(@id, waiter)
98
+ rescue => failure
99
+ @completion.fail(failure)
100
+ end
101
+
102
+ private
103
+
104
+ def read_execution
105
+ begin
106
+ execution = @agent.persistence.executions.load(@id)
107
+ rescue Phronomy::Persistence::NotFoundError
108
+ return nil
109
+ end
110
+ unless execution.agent_id == @agent.agent_id
111
+ raise Phronomy::Persistence::ConflictError, "Reserved execution #{@id} belongs to another Agent"
112
+ end
113
+ expected = @config[:phronomy_coordination]
114
+ stored = execution.metadata["coordination"]
115
+ if expected && stored != expected
116
+ actual_identity = stored&.except("handoff_revision")
117
+ expected_identity = expected.except("handoff_revision")
118
+ unless actual_identity == expected_identity
119
+ raise Phronomy::Persistence::ConflictError, "Reserved execution #{@id} coordination mismatch"
120
+ end
121
+ end
122
+ ref = execution.metadata["current_input_ref"]
123
+ if ref && @agent.persistence.contents.fetch_text(ref) != @input
124
+ raise Phronomy::Persistence::ConflictError, "Reserved execution #{@id} input mismatch"
125
+ end
126
+ execution
127
+ end
128
+
129
+ def reconcile(failure)
130
+ task = @runtime.offload.submit(on_full: :raise) do
131
+ execution = read_execution
132
+ if execution&.terminal?
133
+ materialize(execution)
134
+ else
135
+ raise(failure || Phronomy::ExecutionRehydrationRequiredError.new(
136
+ "Exact execution #{@id} is unfinished; resume with current wiring"
137
+ ))
138
+ end
139
+ end
140
+ task.on_complete do |result, error|
141
+ error ? @completion.fail(error) : @completion.complete(result)
142
+ end
143
+ rescue => error
144
+ @completion.fail(error)
145
+ end
146
+
147
+ def materialize(execution)
148
+ result = @agent.persistence.execution_result(execution.execution_id)
149
+ result.merge(output: result[:result]).freeze
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Agent
5
+ # Wakes the existing execution cancellation token. Durable owners record the
6
+ # request first; this notification is replaceable after Runtime loss.
7
+ # @api private
8
+ class ExecutionCancellation
9
+ Command = Data.define(:coordinator, :execution_id, :agent_id)
10
+ private_constant :Command
11
+
12
+ def self.signal(execution_id, agent_id)
13
+ command = Command.new(coordinator: new, execution_id: execution_id, agent_id: agent_id)
14
+ Phronomy::Runtime.instance.event_loop.post(Phronomy::Event.new(type: :agent_terminal_ready,
15
+ target_id: Phronomy::EventLoop::SYSTEM_CHANNEL_ID, payload: {command: command}))
16
+ end
17
+
18
+ def deliver_on_event_loop(command)
19
+ state = Phronomy::Runtime.instance.event_loop.agent_execution_state(command.execution_id)
20
+ return unless state && state.agent.agent_id == command.agent_id
21
+ state.invocation&.config&.fetch(:cancellation_token, nil)&.cancel!
22
+ end
23
+ end
24
+ end
25
+ end