phronomy 0.7.1 → 0.9.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 (110) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -45
  3. data/benchmark/baseline.json +1 -1
  4. data/benchmark/bench_agent_invoke.rb +1 -1
  5. data/benchmark/bench_context_assembler.rb +11 -3
  6. data/benchmark/bench_regression.rb +11 -11
  7. data/benchmark/bench_token_estimator.rb +5 -5
  8. data/benchmark/bench_tool_schema.rb +2 -2
  9. data/docs/decisions/011-build-context-as-single-llm-input-authority.md +224 -0
  10. data/lib/phronomy/agent/base.rb +268 -403
  11. data/lib/phronomy/agent/checkpoint.rb +118 -0
  12. data/lib/phronomy/agent/concerns/suspendable.rb +6 -6
  13. data/lib/phronomy/agent/context/capability/base.rb +689 -0
  14. data/lib/phronomy/agent/context/capability/scope_policy.rb +54 -0
  15. data/lib/phronomy/agent/context/instruction/prompt_template.rb +102 -0
  16. data/lib/phronomy/agent/context/knowledge/base.rb +58 -0
  17. data/lib/phronomy/agent/context/knowledge/entity_knowledge.rb +102 -0
  18. data/lib/phronomy/agent/context/knowledge/static_knowledge.rb +58 -0
  19. data/lib/phronomy/agent/fsm.rb +1 -1
  20. data/lib/phronomy/agent/invocation_pipeline.rb +108 -0
  21. data/lib/phronomy/agent/lifecycle/fsm_session.rb +251 -0
  22. data/lib/phronomy/agent/lifecycle/phase_machine_builder.rb +249 -0
  23. data/lib/phronomy/agent/react_agent.rb +43 -37
  24. data/lib/phronomy/agent/runner.rb +2 -2
  25. data/lib/phronomy/agent/shared_state.rb +2 -2
  26. data/lib/phronomy/agent/tool_executor.rb +108 -0
  27. data/lib/phronomy/concurrency/async_queue.rb +157 -0
  28. data/lib/phronomy/concurrency/blocking_adapter_pool.rb +443 -0
  29. data/lib/phronomy/concurrency/cancellation_scope.rb +125 -0
  30. data/lib/phronomy/concurrency/cancellation_token.rb +140 -0
  31. data/lib/phronomy/concurrency/concurrency_gate.rb +157 -0
  32. data/lib/phronomy/concurrency/deadline.rb +65 -0
  33. data/lib/phronomy/{runtime → concurrency}/gate_registry.rb +1 -2
  34. data/lib/phronomy/{runtime → concurrency}/pool_registry.rb +1 -1
  35. data/lib/phronomy/configuration.rb +0 -6
  36. data/lib/phronomy/context.rb +2 -8
  37. data/lib/phronomy/eval/runner.rb +4 -0
  38. data/lib/phronomy/eval/scorer/llm_judge.rb +12 -1
  39. data/lib/phronomy/event_loop.rb +7 -7
  40. data/lib/phronomy/invocation_context.rb +3 -3
  41. data/lib/phronomy/knowledge_source.rb +0 -5
  42. data/lib/phronomy/llm_adapter/ruby_llm.rb +17 -11
  43. data/lib/phronomy/llm_context_window/assembler.rb +191 -0
  44. data/lib/phronomy/{context → llm_context_window}/context_version_cache.rb +1 -1
  45. data/lib/phronomy/{context → llm_context_window}/token_budget.rb +7 -4
  46. data/lib/phronomy/{context → llm_context_window}/token_estimator.rb +3 -3
  47. data/lib/phronomy/{agent → multi_agent}/handoff.rb +6 -6
  48. data/lib/phronomy/{agent → multi_agent}/orchestrator.rb +7 -7
  49. data/lib/phronomy/{agent → multi_agent}/parallel_tool_chat.rb +4 -4
  50. data/lib/phronomy/{agent → multi_agent}/team_coordinator.rb +4 -4
  51. data/lib/phronomy/runtime/runtime_metrics.rb +0 -1
  52. data/lib/phronomy/runtime.rb +20 -6
  53. data/lib/phronomy/task_group.rb +1 -1
  54. data/lib/phronomy/tool.rb +3 -4
  55. data/lib/phronomy/{tool/agent_tool.rb → tools/agent.rb} +6 -6
  56. data/lib/phronomy/{tool/mcp_tool.rb → tools/mcp.rb} +9 -9
  57. data/lib/phronomy/tools/vector_search.rb +70 -0
  58. data/lib/phronomy/tracing/null_tracer.rb +3 -1
  59. data/lib/phronomy/vector_store/async_backend.rb +4 -4
  60. data/lib/phronomy/vector_store/base.rb +2 -2
  61. data/lib/phronomy/vector_store/embeddings/base.rb +41 -0
  62. data/lib/phronomy/vector_store/embeddings/ruby_llm_embeddings.rb +47 -0
  63. data/lib/phronomy/vector_store/in_memory.rb +12 -2
  64. data/lib/phronomy/vector_store/loader/base.rb +27 -0
  65. data/lib/phronomy/vector_store/loader/csv_loader.rb +58 -0
  66. data/lib/phronomy/vector_store/loader/markdown_loader.rb +78 -0
  67. data/lib/phronomy/vector_store/loader/plain_text_loader.rb +24 -0
  68. data/lib/phronomy/vector_store/pgvector.rb +2 -2
  69. data/lib/phronomy/vector_store/redis_search.rb +2 -2
  70. data/lib/phronomy/vector_store/splitter/base.rb +49 -0
  71. data/lib/phronomy/vector_store/splitter/fixed_size_splitter.rb +53 -0
  72. data/lib/phronomy/vector_store/splitter/recursive_splitter.rb +107 -0
  73. data/lib/phronomy/vector_store.rb +14 -2
  74. data/lib/phronomy/version.rb +1 -1
  75. data/lib/phronomy/workflow_context.rb +8 -0
  76. data/lib/phronomy/workflow_runner.rb +11 -131
  77. data/lib/phronomy.rb +2 -0
  78. data/scripts/api_snapshot.rb +11 -9
  79. metadata +44 -46
  80. data/lib/phronomy/async_queue.rb +0 -155
  81. data/lib/phronomy/blocking_adapter_pool.rb +0 -435
  82. data/lib/phronomy/cancellation_scope.rb +0 -123
  83. data/lib/phronomy/cancellation_token.rb +0 -133
  84. data/lib/phronomy/concurrency_gate.rb +0 -155
  85. data/lib/phronomy/context/assembler.rb +0 -143
  86. data/lib/phronomy/context/compaction_context.rb +0 -111
  87. data/lib/phronomy/context/trigger_context.rb +0 -39
  88. data/lib/phronomy/context/trim_context.rb +0 -75
  89. data/lib/phronomy/deadline.rb +0 -63
  90. data/lib/phronomy/embeddings/base.rb +0 -39
  91. data/lib/phronomy/embeddings/ruby_llm_embeddings.rb +0 -45
  92. data/lib/phronomy/embeddings.rb +0 -11
  93. data/lib/phronomy/fsm_session.rb +0 -247
  94. data/lib/phronomy/knowledge_source/base.rb +0 -54
  95. data/lib/phronomy/knowledge_source/entity_knowledge.rb +0 -96
  96. data/lib/phronomy/knowledge_source/rag_knowledge.rb +0 -57
  97. data/lib/phronomy/knowledge_source/static_knowledge.rb +0 -52
  98. data/lib/phronomy/loader/base.rb +0 -25
  99. data/lib/phronomy/loader/csv_loader.rb +0 -56
  100. data/lib/phronomy/loader/markdown_loader.rb +0 -76
  101. data/lib/phronomy/loader/plain_text_loader.rb +0 -22
  102. data/lib/phronomy/loader.rb +0 -13
  103. data/lib/phronomy/prompt_template.rb +0 -96
  104. data/lib/phronomy/splitter/base.rb +0 -47
  105. data/lib/phronomy/splitter/fixed_size_splitter.rb +0 -51
  106. data/lib/phronomy/splitter/recursive_splitter.rb +0 -105
  107. data/lib/phronomy/splitter.rb +0 -12
  108. data/lib/phronomy/tool/base.rb +0 -644
  109. data/lib/phronomy/tool/scope_policy.rb +0 -50
  110. data/lib/phronomy/tool_executor.rb +0 -106
@@ -56,6 +56,124 @@ module Phronomy
56
56
  @pending_tool_args = pending_tool_args
57
57
  @pending_tool_call_id = pending_tool_call_id
58
58
  end
59
+
60
+ # Converts this checkpoint to a plain Hash suitable for JSON / Marshal serialization.
61
+ #
62
+ # All values are plain Ruby objects (String, Symbol, Hash, Array, Numeric,
63
+ # nil). +RubyLLM::Message+ objects in +:messages+ are deep-converted so that
64
+ # any embedded +RubyLLM::ToolCall+ objects are also serialized as plain hashes.
65
+ #
66
+ # @example Round-trip via JSON
67
+ # json = JSON.generate(checkpoint.to_h)
68
+ # checkpoint2 = Phronomy::Agent::Checkpoint.from_h(JSON.parse(json))
69
+ #
70
+ # @return [Hash]
71
+ # @api public
72
+ def to_h
73
+ {
74
+ thread_id: @thread_id,
75
+ original_input: @original_input,
76
+ messages: @messages.map { |m| serialize_message(m) },
77
+ pending_tool_name: @pending_tool_name,
78
+ pending_tool_args: @pending_tool_args,
79
+ pending_tool_call_id: @pending_tool_call_id
80
+ }
81
+ end
82
+
83
+ # Reconstructs a +Checkpoint+ from a plain Hash (e.g. produced by {#to_h}
84
+ # and deserialized from JSON or Marshal).
85
+ #
86
+ # Hash keys may be either Symbols or Strings; both are accepted.
87
+ # +RubyLLM::ToolCall+ objects inside message +:tool_calls+ arrays are
88
+ # reconstructed from their hash representations.
89
+ #
90
+ # @param h [Hash] a hash previously produced by {#to_h}
91
+ # @return [Checkpoint]
92
+ # @api public
93
+ def self.from_h(h)
94
+ h = h.transform_keys { |k|
95
+ begin
96
+ k.to_sym
97
+ rescue
98
+ k
99
+ end
100
+ }
101
+ messages = Array(h[:messages]).map { |m| deserialize_message(m) }
102
+ new(
103
+ thread_id: h[:thread_id],
104
+ original_input: h[:original_input],
105
+ messages: messages,
106
+ pending_tool_name: h[:pending_tool_name]&.to_s,
107
+ pending_tool_args: h[:pending_tool_args] ? h[:pending_tool_args].transform_keys { |k|
108
+ begin
109
+ k.to_sym
110
+ rescue
111
+ k
112
+ end
113
+ } : {},
114
+ pending_tool_call_id: h[:pending_tool_call_id]&.to_s
115
+ )
116
+ end
117
+
118
+ private
119
+
120
+ # Converts a +RubyLLM::Message+ to a plain Hash, ensuring that any
121
+ # embedded +RubyLLM::ToolCall+ objects in +:tool_calls+ are also converted.
122
+ #
123
+ # @param msg [RubyLLM::Message]
124
+ # @return [Hash]
125
+ # @api private
126
+ def serialize_message(msg)
127
+ h = msg.to_h
128
+ return h unless h[:tool_calls]
129
+
130
+ h.merge(tool_calls: h[:tool_calls].map { |tc|
131
+ tc.respond_to?(:to_h) ? tc.to_h : tc
132
+ })
133
+ end
134
+
135
+ # Reconstructs a +RubyLLM::Message+ from a plain Hash.
136
+ # +RubyLLM::ToolCall+ entries in +:tool_calls+ are re-instantiated.
137
+ #
138
+ # @param h [Hash]
139
+ # @return [RubyLLM::Message]
140
+ # @api private
141
+ def self.deserialize_message(h)
142
+ h = h.transform_keys { |k|
143
+ begin
144
+ k.to_sym
145
+ rescue
146
+ k
147
+ end
148
+ }
149
+ if h[:tool_calls]
150
+ h = h.merge(tool_calls: Array(h[:tool_calls]).map { |tc|
151
+ next tc if tc.is_a?(RubyLLM::ToolCall)
152
+
153
+ tc = tc.transform_keys { |k|
154
+ begin
155
+ k.to_sym
156
+ rescue
157
+ k
158
+ end
159
+ }
160
+ RubyLLM::ToolCall.new(
161
+ id: tc[:id].to_s,
162
+ name: tc[:name].to_s,
163
+ arguments: (tc[:arguments] || {}).transform_keys { |k|
164
+ begin
165
+ k.to_sym
166
+ rescue
167
+ k
168
+ end
169
+ },
170
+ thought_signature: tc[:thought_signature]
171
+ )
172
+ })
173
+ end
174
+ RubyLLM::Message.new(h)
175
+ end
176
+ private_class_method :deserialize_message
59
177
  end
60
178
  end
61
179
  end
@@ -65,12 +65,12 @@ module Phronomy
65
65
  # Build a fresh chat with all tools registered.
66
66
  chat = build_chat
67
67
 
68
- # Re-apply system instructions so the LLM has the same persona/context
69
- # as the original invocation. build_cached_system_text is memoised, so
70
- # a Proc- or PromptTemplate-based instructions block is re-evaluated
71
- # against the original input rather than using a stale cached value.
72
- system_text = build_cached_system_text(checkpoint.original_input)
73
- apply_instructions(chat, system_text) if system_text
68
+ # Re-apply system instructions and register tools so the LLM has the
69
+ # same persona/context as the original invocation. build_context
70
+ # includes all tool classes (static + handoff) via add_capability.
71
+ context = build_context(checkpoint.original_input, messages: [])
72
+ apply_instructions(chat, context[:system]) if context[:system]
73
+ (context[:tool_classes] || []).each { |tc| chat.with_tool(prepare_tool_class(tc)) }
74
74
 
75
75
  # Restore the full conversation (history + user + assistant with tool call).
76
76
  checkpoint.messages.each { |msg| chat.messages << msg }