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,226 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ class Persistence
5
+ module Migration
6
+ # Explicit logical conversion from the pre-S3 unversioned representations
7
+ # present at the ACS-06 fixed base to the initial 0.1 durable formats.
8
+ #
9
+ # This module does not scan a database or write records. A backend-specific
10
+ # migration runner is responsible for physical enumeration, transactions,
11
+ # and replacement. Normal Runtime load never calls these methods.
12
+ #
13
+ # @api public
14
+ module InitialFormatMigration
15
+ PRE_S3_AGENT_ROOT_KEYS = Phronomy::Agent::AgentRoot::ATTRIBUTES.map(&:to_s).freeze
16
+ PRE_S3_AGENT_EXECUTION_KEYS = Phronomy::Agent::AgentExecution::ATTRIBUTES.map(&:to_s).freeze
17
+ PRE_S3_JOURNAL_KEYS = Phronomy::Agent::JournalRecord::ATTRIBUTES.map(&:to_s).freeze
18
+ PRE_S3_LLM_CALL_KEYS = Phronomy::Agent::LLMCallRecord::ATTRIBUTES.map(&:to_s).freeze
19
+ PRE_S3_APPROVAL_REQUIRED_KEYS = %w[id items created_at].freeze
20
+ PRE_S3_APPROVAL_PARENT_KEYS = %w[execution_id agent_invocation_id].freeze
21
+ PRE_S3_APPROVAL_OPTIONAL_KEYS = %w[approved].freeze
22
+ PRE_S3_APPROVAL_ITEM_KEYS = %w[
23
+ tool_invocation_id tool_call_id tool_name arguments facts reason origin metadata
24
+ ].freeze
25
+
26
+ module_function
27
+
28
+ def agent_root(hash)
29
+ source = stringify_keys(hash)
30
+ validate_exact_keys!(
31
+ source,
32
+ PRE_S3_AGENT_ROOT_KEYS,
33
+ label: "pre-S3 AgentRoot"
34
+ )
35
+ DurableCodec.encode_agent_root(
36
+ Phronomy::Agent::AgentRoot.from_h(source)
37
+ )
38
+ rescue Phronomy::Persistence::SerializationError
39
+ raise
40
+ rescue => error
41
+ migration_error("AgentRoot", error)
42
+ end
43
+
44
+ def agent_execution(hash)
45
+ source = stringify_keys(hash)
46
+ validate_exact_keys!(
47
+ source,
48
+ PRE_S3_AGENT_EXECUTION_KEYS,
49
+ label: "pre-S3 AgentExecution"
50
+ )
51
+ source["working_records"] = Array(source.fetch("working_records")).map.with_index do |record, index|
52
+ normalize_legacy_journal_record(
53
+ record,
54
+ label: "pre-S3 AgentExecution working_records[#{index}]"
55
+ )
56
+ end
57
+ source["llm_calls"] = Array(source.fetch("llm_calls")).map.with_index do |call, index|
58
+ normalized = stringify_keys(call)
59
+ validate_exact_keys!(
60
+ normalized,
61
+ PRE_S3_LLM_CALL_KEYS,
62
+ label: "pre-S3 AgentExecution llm_calls[#{index}]"
63
+ )
64
+ normalized
65
+ end
66
+ source["approval_request"] = normalize_legacy_approval_request(
67
+ source["approval_request"],
68
+ execution_id: source.fetch("execution_id")
69
+ )
70
+ execution = Phronomy::Agent::AgentExecution.from_h(source)
71
+ DurableCodec.encode_agent_execution(execution)
72
+ rescue Phronomy::Persistence::SerializationError
73
+ raise
74
+ rescue => error
75
+ migration_error("AgentExecution", error)
76
+ end
77
+
78
+ def journal_record(hash)
79
+ source = normalize_legacy_journal_record(hash, label: "pre-S3 JournalRecord")
80
+ DurableCodec.encode_journal_record(
81
+ Phronomy::Agent::JournalRecord.from_h(source)
82
+ )
83
+ rescue Phronomy::Persistence::SerializationError
84
+ raise
85
+ rescue => error
86
+ migration_error("JournalRecord", error)
87
+ end
88
+
89
+ def workflow_state(workflow_instance_id:, revision:, snapshot:)
90
+ DurableCodec.encode_workflow_state(
91
+ workflow_instance_id: workflow_instance_id,
92
+ workflow_revision: Integer(revision),
93
+ snapshot: snapshot
94
+ )
95
+ rescue Phronomy::Persistence::SerializationError
96
+ raise
97
+ rescue => error
98
+ migration_error("Workflow state", error)
99
+ end
100
+
101
+ # LLMInputManifest is a ContentStore codec boundary, not a
102
+ # Persistence::DurableRecord. Its pre-CG-07 integer version 1 is migrated
103
+ # explicitly to the pre-1.0 string version "0.1".
104
+ def llm_input_manifest(hash)
105
+ source = stringify_keys(hash)
106
+ old_version = source.fetch("version")
107
+ unless old_version == 1 || old_version == "1"
108
+ raise Phronomy::Persistence::SerializationError,
109
+ "unsupported pre-S3 LLMInputManifest version: #{old_version.inspect}"
110
+ end
111
+ source["version"] = Phronomy::Agent::LLMInputManifest::VERSION
112
+ Phronomy::Agent::LLMInputManifest.from_h(source).to_h
113
+ rescue Phronomy::Persistence::SerializationError
114
+ raise
115
+ rescue => error
116
+ migration_error("LLMInputManifest", error)
117
+ end
118
+
119
+ def normalize_legacy_journal_record(hash, label:)
120
+ source = stringify_keys(hash)
121
+ validate_allowed_keys!(
122
+ source,
123
+ required_keys: PRE_S3_JOURNAL_KEYS,
124
+ optional_keys: ["correlation_id"],
125
+ label: label
126
+ )
127
+ source.delete("correlation_id")
128
+ source
129
+ end
130
+ private_class_method :normalize_legacy_journal_record
131
+
132
+ def normalize_legacy_approval_request(request, execution_id:)
133
+ return nil unless request
134
+
135
+ source = stringify_keys(request)
136
+ validate_allowed_keys!(
137
+ source,
138
+ required_keys: PRE_S3_APPROVAL_REQUIRED_KEYS,
139
+ optional_keys: PRE_S3_APPROVAL_PARENT_KEYS + PRE_S3_APPROVAL_OPTIONAL_KEYS,
140
+ label: "pre-S3 approval_request"
141
+ )
142
+ parents = PRE_S3_APPROVAL_PARENT_KEYS.select { |key| source.key?(key) }
143
+ unless parents.length == 1
144
+ raise Phronomy::Persistence::SerializationError,
145
+ "pre-S3 approval_request must contain exactly one parent identity, got #{parents.inspect}"
146
+ end
147
+
148
+ items = source.fetch("items")
149
+ unless items.is_a?(Array) && !items.empty?
150
+ raise Phronomy::Persistence::SerializationError,
151
+ "pre-S3 approval_request items must be a non-empty Array"
152
+ end
153
+ source["items"] = items.map.with_index do |item, index|
154
+ normalized = stringify_keys(item)
155
+ validate_exact_keys!(
156
+ normalized,
157
+ PRE_S3_APPROVAL_ITEM_KEYS,
158
+ label: "pre-S3 approval_request items[#{index}]"
159
+ )
160
+ normalized
161
+ end
162
+
163
+ if source.key?("agent_invocation_id")
164
+ source.delete("agent_invocation_id")
165
+ source["execution_id"] = execution_id.to_s
166
+ elsif source.fetch("execution_id").to_s != execution_id.to_s
167
+ raise Phronomy::Persistence::SerializationError,
168
+ "pre-S3 approval_request execution_id does not match AgentExecution"
169
+ end
170
+ source
171
+ end
172
+ private_class_method :normalize_legacy_approval_request
173
+
174
+ def validate_allowed_keys!(hash, required_keys:, optional_keys:, label:)
175
+ actual = hash.keys.sort
176
+ missing = required_keys.sort - actual
177
+ unknown = actual - (required_keys + optional_keys).sort
178
+ return hash if missing.empty? && unknown.empty?
179
+
180
+ details = []
181
+ details << "missing=#{missing.inspect}" unless missing.empty?
182
+ details << "unknown=#{unknown.inspect}" unless unknown.empty?
183
+ raise Phronomy::Persistence::SerializationError,
184
+ "#{label} schema mismatch (#{details.join(", ")})"
185
+ end
186
+ private_class_method :validate_allowed_keys!
187
+
188
+ def validate_exact_keys!(hash, expected_keys, label:)
189
+ validate_allowed_keys!(
190
+ hash,
191
+ required_keys: expected_keys,
192
+ optional_keys: [],
193
+ label: label
194
+ )
195
+ end
196
+ private_class_method :validate_exact_keys!
197
+
198
+ def stringify_keys(hash)
199
+ unless hash.is_a?(Hash)
200
+ raise Phronomy::Persistence::SerializationError,
201
+ "migration input must be a Hash"
202
+ end
203
+ hash.each_with_object({}) do |(key, value), result|
204
+ unless key.is_a?(String) || key.is_a?(Symbol)
205
+ raise Phronomy::Persistence::SerializationError,
206
+ "migration key must be String or Symbol, got #{key.class}"
207
+ end
208
+ string_key = key.to_s
209
+ if result.key?(string_key)
210
+ raise Phronomy::Persistence::SerializationError,
211
+ "duplicate migration key after normalization: #{string_key.inspect}"
212
+ end
213
+ result[string_key] = value
214
+ end
215
+ end
216
+ private_class_method :stringify_keys
217
+
218
+ def migration_error(label, error)
219
+ raise Phronomy::Persistence::SerializationError,
220
+ "cannot migrate pre-S3 #{label}: #{error.class}: #{error.message}"
221
+ end
222
+ private_class_method :migration_error
223
+ end
224
+ end
225
+ end
226
+ end