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
@@ -4,6 +4,15 @@ module Phronomy
4
4
  module Agent
5
5
  # Implements peer coordination through a shared KnowledgeStore.
6
6
  class SharedState
7
+ # Semantic revision of the framework-owned SharedState instrumentation
8
+ # applied on top of one specific researcher definition revision.
9
+ #
10
+ # The generated definition ID below already includes the wrapped
11
+ # researcher's definition ID/version. Increment this value only when the
12
+ # semantics of SharedState's injected coordination capabilities change.
13
+ INSTRUMENTATION_DEFINITION_VERSION = 1
14
+ private_constant :INSTRUMENTATION_DEFINITION_VERSION
15
+
7
16
  class KnowledgeStore
8
17
  def initialize
9
18
  @findings = []
@@ -168,7 +177,35 @@ module Phronomy
168
177
  definitions[read_tool] = nil
169
178
  definitions[write_tool] = nil
170
179
 
171
- Class.new(researcher_class) { tools(definitions) }
180
+ # The anonymous subclass has a different effective Agent definition from the
181
+ # configured researcher because SharedState adds LLM-visible coordination
182
+ # Tools. D04 therefore requires a distinct semantic definition identity
183
+ # instead of reusing the researcher's exact definition revision.
184
+ #
185
+ # This is a framework-private generated lineage. It is derived from the
186
+ # wrapped researcher's *definition revision*, while its own version tracks the
187
+ # semantic revision of the SharedState instrumentation itself. This keeps the
188
+ # application's definition-version namespace independent from framework
189
+ # instrumentation.
190
+ instrumented_def = instrumented_definition_for(researcher_class)
191
+ Class.new(researcher_class) do
192
+ agent_definition(
193
+ id: instrumented_def.fetch(:id),
194
+ version: instrumented_def.fetch(:version)
195
+ )
196
+ tools(definitions)
197
+ end
198
+ end
199
+
200
+ def instrumented_definition_for(researcher_class)
201
+ parent_def = researcher_class.agent_definition
202
+ parent_id = parent_def.fetch(:id)
203
+ parent_version = parent_def.fetch(:version)
204
+
205
+ {
206
+ id: "Phronomy::Agent::SharedState::Instrumented/#{parent_id}@#{parent_version}".freeze,
207
+ version: INSTRUMENTATION_DEFINITION_VERSION
208
+ }.freeze
172
209
  end
173
210
 
174
211
  def build_prompt(
@@ -45,6 +45,20 @@ module Phronomy
45
45
  freeze
46
46
  end
47
47
 
48
+ def self.from_h(hash)
49
+ values = hash.to_h { |key, value| [key.to_s, value] }
50
+ new(
51
+ tool_invocation_id: values.fetch("tool_invocation_id"),
52
+ tool_call_id: values["tool_call_id"],
53
+ tool_name: values.fetch("tool_name"),
54
+ arguments: values.fetch("arguments", {}),
55
+ facts: values.fetch("facts", {}),
56
+ reason: values["reason"],
57
+ origin: values.fetch("origin", "local"),
58
+ metadata: values.fetch("metadata", {})
59
+ )
60
+ end
61
+
48
62
  def to_h
49
63
  {
50
64
  tool_invocation_id: @tool_invocation_id,
@@ -74,12 +88,23 @@ module Phronomy
74
88
  end
75
89
  end
76
90
 
77
- attr_reader :id, :agent_invocation_id, :items, :created_at
91
+ attr_reader :id, :execution_id, :items, :created_at
92
+
93
+ def self.from_h(hash)
94
+ source = hash.to_h { |key, value| [key.to_s, value] }
95
+ items = Array(source.fetch("items")).map { |item| Item.from_h(item) }
96
+ new(
97
+ execution_id: source.fetch("execution_id"),
98
+ items: items,
99
+ id: source.fetch("id"),
100
+ created_at: Time.iso8601(source.fetch("created_at"))
101
+ )
102
+ end
78
103
 
79
104
  def self.build(agent_invocation)
80
105
  pending = agent_invocation.tool_invocations.select(&:awaiting_approval?)
81
106
  new(
82
- agent_invocation_id: agent_invocation.id,
107
+ execution_id: agent_invocation.execution_id,
83
108
  items: pending.map { |invocation| build_item(invocation) }
84
109
  )
85
110
  end
@@ -98,11 +123,14 @@ module Phronomy
98
123
  end
99
124
  private_class_method :build_item
100
125
 
101
- def initialize(agent_invocation_id:, items:, id: SecureRandom.uuid, created_at: Time.now.utc)
126
+ def initialize(execution_id:, items:, id: SecureRandom.uuid, created_at: Time.now.utc)
102
127
  raise ArgumentError, "ToolApprovalRequest requires at least one item" if items.empty?
128
+ if execution_id.nil? || execution_id.to_s.empty?
129
+ raise ArgumentError, "ToolApprovalRequest requires execution_id"
130
+ end
103
131
 
104
132
  @id = id.to_s.freeze
105
- @agent_invocation_id = agent_invocation_id.to_s.freeze
133
+ @execution_id = execution_id.to_s.freeze
106
134
  @items = items.dup.freeze
107
135
  @created_at = created_at
108
136
  freeze
@@ -111,7 +139,7 @@ module Phronomy
111
139
  def to_h
112
140
  {
113
141
  id: @id,
114
- agent_invocation_id: @agent_invocation_id,
142
+ execution_id: @execution_id,
115
143
  items: @items.map(&:to_h),
116
144
  created_at: @created_at.iso8601
117
145
  }
@@ -5,8 +5,8 @@ module Phronomy
5
5
  class ToolDefinitionSet
6
6
  attr_reader :runtime_tools, :definitions
7
7
 
8
- def self.build(agent)
9
- runtime_tools = (agent.class.tools + agent.send(:_handoff_tools)).freeze
8
+ def self.build(agent, additional_tools: [])
9
+ runtime_tools = (agent.class.tools + Array(additional_tools)).freeze
10
10
  definitions = runtime_tools.map do |tool_class|
11
11
  prepared = agent.send(:prepare_tool_class, tool_class)
12
12
  tool = prepared.is_a?(Class) ? prepared.new : prepared
@@ -46,10 +46,56 @@ module Phronomy
46
46
  end
47
47
 
48
48
  def initialize(runtime_tools:, definitions:)
49
- @runtime_tools = runtime_tools
49
+ @runtime_tools = Array(runtime_tools).freeze
50
50
  @definitions = Immutable.copy(definitions)
51
+ validate_unique_names!
51
52
  freeze
52
53
  end
54
+
55
+ def select_definitions(expected_definitions)
56
+ expected = Immutable.copy(Array(expected_definitions))
57
+ current_by_name = definitions.each_with_index.to_h do |definition, index|
58
+ [definition.fetch("name"), [definition, runtime_tools.fetch(index)]]
59
+ end
60
+ seen = {}
61
+ selected_tools = []
62
+ selected_definitions = []
63
+
64
+ expected.each do |definition|
65
+ name = definition.fetch("name").to_s
66
+ raise ArgumentError, "duplicate selected Tool definition: #{name}" if seen[name]
67
+ seen[name] = true
68
+
69
+ current_definition, runtime_tool = current_by_name.fetch(name) do
70
+ raise Phronomy::ConfigurationError,
71
+ "ContextPolicy selected Tool not present in current Agent configuration: #{name}"
72
+ end
73
+ unless Phronomy::CanonicalJSON.dump(current_definition) ==
74
+ Phronomy::CanonicalJSON.dump(definition)
75
+ raise Phronomy::ConfigurationError,
76
+ "Agent Tool definition changed after ContextPolicy selection: #{name}"
77
+ end
78
+
79
+ selected_tools << runtime_tool
80
+ selected_definitions << current_definition
81
+ end
82
+
83
+ self.class.new(
84
+ runtime_tools: selected_tools,
85
+ definitions: selected_definitions
86
+ )
87
+ end
88
+
89
+ private
90
+
91
+ def validate_unique_names!
92
+ names = definitions.map { |definition| definition.fetch("name").to_s }
93
+ duplicates = names.group_by(&:itself).select { |_name, values| values.length > 1 }.keys
94
+ return if duplicates.empty?
95
+
96
+ raise Phronomy::ConfigurationError,
97
+ "duplicate effective Tool definition name(s): #{duplicates.inspect}"
98
+ end
53
99
  end
54
100
  end
55
101
  end