phronomy 0.19.0 → 0.21.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +3 -1
  3. data/CHANGELOG.md +41 -11
  4. data/CONTRIBUTING.md +74 -6
  5. data/README.md +15 -6
  6. data/docs/decisions/010-cooperative-first-concurrency.md +86 -69
  7. data/docs/decisions/014-unified-persistence-durable-state.md +5 -0
  8. data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +184 -0
  9. data/docs/features.md +31 -2
  10. data/docs/getting-started.md +10 -1
  11. data/docs/migrations/0.19.md +12 -6
  12. data/docs/persistence-backends.md +504 -0
  13. data/docs/runtime-and-concurrency.md +110 -147
  14. data/lib/phronomy/agent/agent_execution.rb +29 -0
  15. data/lib/phronomy/agent/llm_call_record.rb +20 -0
  16. data/lib/phronomy/agent/tool_executor.rb +7 -3
  17. data/lib/phronomy/engine/concurrency/offload_pool.rb +142 -245
  18. data/lib/phronomy/engine/task.rb +50 -6
  19. data/lib/phronomy/invocation_context.rb +11 -52
  20. data/lib/phronomy/llm_adapter/base.rb +29 -32
  21. data/lib/phronomy/llm_adapter/ruby_llm.rb +13 -12
  22. data/lib/phronomy/llm_adapter.rb +10 -7
  23. data/lib/phronomy/output_parser/base.rb +5 -1
  24. data/lib/phronomy/persistence.rb +101 -7
  25. data/lib/phronomy/testing/persistence_contract/a_content_store.rb +50 -0
  26. data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +164 -0
  27. data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +215 -0
  28. data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +119 -0
  29. data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +99 -0
  30. data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +202 -0
  31. data/lib/phronomy/testing/persistence_contract.rb +41 -0
  32. data/lib/phronomy/tool/base.rb +15 -0
  33. data/lib/phronomy/tool.rb +11 -0
  34. data/lib/phronomy/vector_store/async_backend.rb +15 -38
  35. data/lib/phronomy/vector_store/base.rb +12 -13
  36. data/lib/phronomy/vector_store/embeddings/base.rb +11 -9
  37. data/lib/phronomy/version.rb +1 -1
  38. data/lib/phronomy.rb +6 -0
  39. data/scripts/run_mutation.sh +2 -1
  40. data/sig/phronomy/agent.rbs +36 -0
  41. data/sig/phronomy/extensions.rbs +50 -0
  42. data/sig/phronomy/llm_adapter.rbs +11 -0
  43. data/sig/phronomy/persistence.rbs +70 -0
  44. data/sig/phronomy/runtime.rbs +43 -0
  45. data/sig/phronomy/tool.rbs +39 -0
  46. data/sig/phronomy/workflow.rbs +30 -0
  47. data/sig/phronomy.rbs +49 -1
  48. metadata +20 -3
  49. data/scripts/check_private_enforcement.rb +0 -93
@@ -0,0 +1,202 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ RSpec.shared_examples "an Execution repository" do
6
+ let(:execution_repository) { persistence.executions }
7
+ let(:execution_agent_root) do
8
+ Phronomy::Agent::AgentRoot.create(
9
+ agent_id: "execution-agent-#{SecureRandom.uuid}",
10
+ agent_definition_id: "contract-agent",
11
+ definition_version: 1
12
+ )
13
+ end
14
+
15
+ def build_contract_execution(root)
16
+ input_record = Phronomy::Agent::JournalRecord.new(
17
+ agent_id: root.agent_id,
18
+ kind: :input_received,
19
+ channel: :external,
20
+ role: :user,
21
+ context_candidate: false
22
+ )
23
+ Phronomy::Agent::AgentExecution.start(
24
+ agent_root: root,
25
+ input_record: input_record
26
+ )
27
+ end
28
+
29
+ before do
30
+ persistence.agents.create(execution_agent_root)
31
+ end
32
+
33
+ it "creates and loads an active execution" do
34
+ execution = build_contract_execution(execution_agent_root)
35
+
36
+ expect(execution_repository.create_active(execution).to_h).to eq(execution.to_h)
37
+ expect(execution_repository.load(execution.execution_id).to_h).to eq(execution.to_h)
38
+ end
39
+
40
+ it "raises NotFoundError for a missing execution" do
41
+ expect do
42
+ execution_repository.load("missing-#{SecureRandom.uuid}")
43
+ end.to raise_error(Phronomy::Persistence::NotFoundError)
44
+ end
45
+
46
+ it "rejects a duplicate execution_id" do
47
+ execution = build_contract_execution(execution_agent_root)
48
+ execution_repository.create_active(execution)
49
+
50
+ expect do
51
+ execution_repository.create_active(execution)
52
+ end.to raise_error(Phronomy::Persistence::ConflictError)
53
+ end
54
+
55
+ it "admits at most one active or suspended execution for one Agent" do
56
+ first = build_contract_execution(execution_agent_root)
57
+ second = build_contract_execution(execution_agent_root)
58
+ execution_repository.create_active(first)
59
+
60
+ expect do
61
+ execution_repository.create_active(second)
62
+ end.to raise_error(Phronomy::AgentBusyError)
63
+ end
64
+
65
+ it "allows different Agents to have active executions" do
66
+ other_root = Phronomy::Agent::AgentRoot.create(
67
+ agent_id: "execution-agent-#{SecureRandom.uuid}",
68
+ agent_definition_id: "contract-agent",
69
+ definition_version: 1
70
+ )
71
+ persistence.agents.create(other_root)
72
+
73
+ expect do
74
+ execution_repository.create_active(build_contract_execution(execution_agent_root))
75
+ execution_repository.create_active(build_contract_execution(other_root))
76
+ end.not_to raise_error
77
+ end
78
+
79
+ it "saves only at the expected execution revision" do
80
+ execution = build_contract_execution(execution_agent_root)
81
+ execution_repository.create_active(execution)
82
+ updated = execution.with(status: :active, phase: :calling_llm)
83
+
84
+ expect(
85
+ execution_repository.save(
86
+ execution.execution_id,
87
+ expected_revision: 0,
88
+ execution: updated
89
+ ).to_h
90
+ ).to eq(updated.to_h)
91
+ expect(execution_repository.load(execution.execution_id).execution_revision).to eq(1)
92
+ end
93
+
94
+ it "rejects a stale execution revision" do
95
+ execution = build_contract_execution(execution_agent_root)
96
+ execution_repository.create_active(execution)
97
+ updated = execution.with(status: :active, phase: :calling_llm)
98
+ execution_repository.save(
99
+ execution.execution_id,
100
+ expected_revision: 0,
101
+ execution: updated
102
+ )
103
+
104
+ expect do
105
+ execution_repository.save(
106
+ execution.execution_id,
107
+ expected_revision: 0,
108
+ execution: updated
109
+ )
110
+ end.to raise_error(Phronomy::Persistence::ConflictError)
111
+ end
112
+
113
+ it "rejects an execution identity mismatch" do
114
+ execution = build_contract_execution(execution_agent_root)
115
+ execution_repository.create_active(execution)
116
+ other_root = Phronomy::Agent::AgentRoot.create(
117
+ agent_id: "execution-agent-#{SecureRandom.uuid}",
118
+ agent_definition_id: "contract-agent",
119
+ definition_version: 1
120
+ )
121
+ persistence.agents.create(other_root)
122
+ other = build_contract_execution(other_root).with(
123
+ execution_revision: 1,
124
+ status: :active,
125
+ phase: :calling_llm
126
+ )
127
+
128
+ expect do
129
+ execution_repository.save(
130
+ execution.execution_id,
131
+ expected_revision: 0,
132
+ execution: other
133
+ )
134
+ end.to raise_error(Phronomy::Persistence::ConflictError)
135
+ end
136
+
137
+ it "requires execution revision to advance exactly once" do
138
+ execution = build_contract_execution(execution_agent_root)
139
+ execution_repository.create_active(execution)
140
+ skipped = execution.with(
141
+ execution_revision: 2,
142
+ status: :active,
143
+ phase: :calling_llm
144
+ )
145
+
146
+ expect do
147
+ execution_repository.save(
148
+ execution.execution_id,
149
+ expected_revision: 0,
150
+ execution: skipped
151
+ )
152
+ end.to raise_error(Phronomy::Persistence::ConflictError)
153
+ end
154
+
155
+ it "lists active executions for one Agent" do
156
+ execution = build_contract_execution(execution_agent_root)
157
+ execution_repository.create_active(execution)
158
+
159
+ expect(execution_repository.list_active(execution_agent_root.agent_id).map(&:execution_id))
160
+ .to eq([execution.execution_id])
161
+ end
162
+
163
+ it "asserts idle state and rejects active Agents" do
164
+ expect do
165
+ execution_repository.assert_idle!(execution_agent_root.agent_id)
166
+ end.not_to raise_error
167
+
168
+ execution_repository.create_active(build_contract_execution(execution_agent_root))
169
+
170
+ expect do
171
+ execution_repository.assert_idle!(execution_agent_root.agent_id)
172
+ end.to raise_error(Phronomy::AgentBusyError)
173
+ end
174
+
175
+ it "deletes one execution" do
176
+ execution = build_contract_execution(execution_agent_root)
177
+ execution_repository.create_active(execution)
178
+ execution_repository.delete(execution.execution_id)
179
+
180
+ expect do
181
+ execution_repository.load(execution.execution_id)
182
+ end.to raise_error(Phronomy::Persistence::NotFoundError)
183
+ end
184
+
185
+ it "deletes all executions for one Agent without deleting other Agents' executions" do
186
+ other_root = Phronomy::Agent::AgentRoot.create(
187
+ agent_id: "execution-agent-#{SecureRandom.uuid}",
188
+ agent_definition_id: "contract-agent",
189
+ definition_version: 1
190
+ )
191
+ persistence.agents.create(other_root)
192
+ own = build_contract_execution(execution_agent_root)
193
+ other = build_contract_execution(other_root)
194
+ execution_repository.create_active(own)
195
+ execution_repository.create_active(other)
196
+
197
+ execution_repository.delete_for_agent(execution_agent_root.agent_id)
198
+
199
+ expect(execution_repository.list_active(execution_agent_root.agent_id)).to be_empty
200
+ expect(execution_repository.load(other.execution_id).execution_id).to eq(other.execution_id)
201
+ end
202
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "phronomy"
4
+
5
+ begin
6
+ require "rspec/core"
7
+ require "rspec/expectations"
8
+ rescue LoadError => error
9
+ raise LoadError,
10
+ "Phronomy Persistence contract support requires RSpec. " \
11
+ "Add `rspec` to the backend project's development/test dependencies before " \
12
+ "requiring `phronomy/testing/persistence_contract`.",
13
+ error.backtrace
14
+ end
15
+
16
+ module Phronomy
17
+ module Testing
18
+ # Explicitly loaded RSpec shared examples for Persistence backend authors.
19
+ #
20
+ # This namespace is intentionally excluded from Phronomy's production
21
+ # Zeitwerk eager-load path. Requiring this file is the opt-in boundary that
22
+ # loads RSpec and the backend conformance suite.
23
+ module PersistenceContract
24
+ SHARED_EXAMPLES = [
25
+ "a persistence content store",
26
+ "an Agent repository",
27
+ "a Journal repository",
28
+ "an Execution repository",
29
+ "a workflow state repository",
30
+ "a Persistence backend"
31
+ ].freeze
32
+ end
33
+ end
34
+ end
35
+
36
+ require_relative "persistence_contract/a_content_store"
37
+ require_relative "persistence_contract/an_agent_repository"
38
+ require_relative "persistence_contract/a_journal_repository"
39
+ require_relative "persistence_contract/an_execution_repository"
40
+ require_relative "persistence_contract/a_workflow_state_repository"
41
+ require_relative "persistence_contract/a_persistence_backend"
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ module Tool
5
+ # Public authoring façade for Phronomy Tools.
6
+ #
7
+ # This constant intentionally refers to the exact same Class object as
8
+ # {Phronomy::Agent::Context::Capability::Base}. The implementation namespace
9
+ # remains canonical internally so the existing Tool DSL state, inheritance,
10
+ # built-in Tools, and compatibility surface are not duplicated.
11
+ #
12
+ # @api public
13
+ Base = Phronomy::Agent::Context::Capability::Base
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ # Public authoring namespace for application-defined Tools.
5
+ #
6
+ # Concrete Tools supplied by Phronomy remain under {Phronomy::Tools}.
7
+ #
8
+ # @api public
9
+ module Tool
10
+ end
11
+ end
@@ -2,43 +2,29 @@
2
2
 
3
3
  module Phronomy
4
4
  module VectorStore
5
- # Mixin that defines the async interface for VectorStore backends.
5
+ # Framework-owned async convenience methods for VectorStore backends.
6
6
  #
7
- # Mixing this module into a VectorStore class provides three choices:
7
+ # The backend extension contract is the synchronous interface defined by
8
+ # {VectorStore::Base}: add/search/remove/clear/size. These async convenience
9
+ # methods are inherited by backends and route that synchronous work through
10
+ # Phronomy's bounded {OffloadPool}. This keeps OS-thread creation, queue
11
+ # backpressure, submit timeout/cancellation, and completion semantics owned by
12
+ # the framework rather than by each backend.
8
13
  #
9
- # 1. **Do nothing** inherits default implementations from {VectorStore::Base}
10
- # that route through {OffloadPool}.
14
+ # A future genuine native-async backend may adapt its completion into a
15
+ # {Phronomy::Task} without an OffloadPool worker, but native async override is
16
+ # not part of the current backend SPI.
11
17
  #
12
- # 2. **Override selectively** — override only the async methods where the
13
- # backend has a native async driver, while the remaining methods fall back
14
- # to the pool.
15
- #
16
- # 3. **Implement all natively** — override all async methods to avoid pool
17
- # allocation entirely.
18
- #
19
- # @example Native async search (no pool worker thread allocated)
20
- # class MyFastStore < Phronomy::VectorStore::Base
21
- # include Phronomy::VectorStore::AsyncBackend
22
- #
23
- # def search_async(query_embedding:, k: 5, cancellation_token: nil, timeout: nil)
24
- # # Returns a PendingOperation backed by a native async driver.
25
- # native_async_search(query_embedding, k)
26
- # end
27
- # end
28
- #
29
- # @api public
18
+ # @api private
30
19
  module AsyncBackend
31
20
  # Async variant of {VectorStore::Base#add}.
32
21
  #
33
- # Submits the add call to {OffloadPool} by default.
34
- # Override to use a native async driver.
35
- #
36
22
  # @param id [String]
37
23
  # @param embedding [Array<Float>]
38
24
  # @param metadata [Hash]
39
25
  # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
40
26
  # @param timeout [Numeric, nil]
41
- # @return [OffloadPool::PendingOperation]
27
+ # @return [Phronomy::Task]
42
28
  # @api public
43
29
  def add_async(id:, embedding:, metadata: {}, cancellation_token: nil, timeout: nil)
44
30
  Phronomy::Runtime.instance.offload.submit(
@@ -52,14 +38,11 @@ module Phronomy
52
38
 
53
39
  # Async variant of {VectorStore::Base#search}.
54
40
  #
55
- # Submits the search call to {OffloadPool} by default.
56
- # Override to use a native async driver.
57
- #
58
41
  # @param query_embedding [Array<Float>]
59
42
  # @param k [Integer]
60
43
  # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
61
44
  # @param timeout [Numeric, nil]
62
- # @return [OffloadPool::PendingOperation]
45
+ # @return [Phronomy::Task]
63
46
  # @api public
64
47
  def search_async(query_embedding:, k: 5, cancellation_token: nil, timeout: nil)
65
48
  Phronomy::Runtime.instance.offload.submit(
@@ -73,13 +56,10 @@ module Phronomy
73
56
 
74
57
  # Async variant of {VectorStore::Base#remove}.
75
58
  #
76
- # Submits the remove call to {OffloadPool} by default.
77
- # Override to use a native async driver.
78
- #
79
59
  # @param id [String]
80
60
  # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
81
61
  # @param timeout [Numeric, nil]
82
- # @return [OffloadPool::PendingOperation]
62
+ # @return [Phronomy::Task]
83
63
  # @api public
84
64
  def remove_async(id:, cancellation_token: nil, timeout: nil)
85
65
  Phronomy::Runtime.instance.offload.submit(
@@ -93,12 +73,9 @@ module Phronomy
93
73
 
94
74
  # Async variant of {VectorStore::Base#clear}.
95
75
  #
96
- # Submits the clear call to {OffloadPool} by default.
97
- # Override to use a native async driver.
98
- #
99
76
  # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
100
77
  # @param timeout [Numeric, nil]
101
- # @return [OffloadPool::PendingOperation]
78
+ # @return [Phronomy::Task]
102
79
  # @api public
103
80
  def clear_async(cancellation_token: nil, timeout: nil)
104
81
  Phronomy::Runtime.instance.offload.submit(
@@ -2,24 +2,22 @@
2
2
 
3
3
  module Phronomy
4
4
  module VectorStore
5
- # Abstract interface for vector stores.
5
+ # Public extension SPI for vector stores.
6
6
  #
7
- # Implementations manage a collection of (embedding, metadata) pairs and
8
- # support similarity search.
7
+ # Backends implement the synchronous add/search/remove/clear/size contract.
8
+ # Phronomy supplies async convenience methods through {AsyncBackend}; blocking
9
+ # backend work is offloaded through the framework-owned bounded OffloadPool.
9
10
  #
10
- # Async methods (`search_async`, `add_async`, `remove_async`, `clear_async`)
11
- # are provided by the {AsyncBackend} mixin which defaults to routing calls
12
- # through {OffloadPool}. Backends with native async drivers may override
13
- # individual async methods without touching the pool at all.
11
+ # @api public
14
12
  class Base
15
13
  include AsyncBackend
16
14
 
17
15
  # Add a document with its vector embedding.
18
16
  #
19
- # @param id [String] unique document identifier
20
- # @param embedding [Array<Float>] vector embedding
21
- # @param metadata [Hash] arbitrary metadata (e.g. the original message object)
22
- # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil] optional; raises CancellationError when cancelled
17
+ # @param id [String] unique document identifier
18
+ # @param embedding [Array<Float>] vector embedding
19
+ # @param metadata [Hash] arbitrary metadata
20
+ # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
23
21
  # @api public
24
22
  def add(id:, embedding:, metadata: {}, cancellation_token: nil)
25
23
  cancellation_token&.raise_if_cancelled!
@@ -29,8 +27,8 @@ module Phronomy
29
27
  # Return the k most similar documents to the query embedding.
30
28
  #
31
29
  # @param query_embedding [Array<Float>]
32
- # @param k [Integer] number of results
33
- # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil] optional; raises CancellationError when cancelled
30
+ # @param k [Integer] number of results
31
+ # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
34
32
  # @return [Array<Hash>] each element: { id:, score:, metadata: }
35
33
  # @api public
36
34
  def search(query_embedding:, k: 5, cancellation_token: nil)
@@ -47,6 +45,7 @@ module Phronomy
47
45
  end
48
46
 
49
47
  # Remove all documents.
48
+ # @api public
50
49
  def clear
51
50
  raise NotImplementedError, "#{self.class}#clear is not implemented"
52
51
  end
@@ -3,15 +3,18 @@
3
3
  module Phronomy
4
4
  module VectorStore
5
5
  module Embeddings
6
- # Abstract interface for embedding adapters.
6
+ # Public extension SPI for embedding adapters.
7
7
  #
8
- # Concrete implementations must override {#embed} and return a vector
9
- # as an +Array<Float>+.
8
+ # Concrete implementations override {#embed}. Phronomy owns the async
9
+ # bridge: {#embed_async} routes the synchronous implementation through the
10
+ # bounded OffloadPool and returns a {Phronomy::Task}.
11
+ #
12
+ # @api public
10
13
  class Base
11
14
  # Embed the given text and return a vector representation.
12
15
  #
13
- # @param text [String] the text to embed
14
- # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil] optional; raises CancellationError when cancelled
16
+ # @param text [String] the text to embed
17
+ # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
15
18
  # @return [Array<Float>] the embedding vector
16
19
  # @api public
17
20
  def embed(text, cancellation_token = nil)
@@ -19,13 +22,12 @@ module Phronomy
19
22
  raise NotImplementedError, "#{self.class}#embed is not implemented"
20
23
  end
21
24
 
22
- # Submits an {#embed} call to {OffloadPool} and returns an
23
- # {OffloadPool::PendingOperation}.
25
+ # Submits an {#embed} call to {OffloadPool}.
24
26
  #
25
27
  # @param text [String]
26
28
  # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
27
- # @param timeout [Numeric, nil] seconds before the operation is abandoned
28
- # @return [OffloadPool::PendingOperation]
29
+ # @param timeout [Numeric, nil] operation-wide submit timeout
30
+ # @return [Phronomy::Task]
29
31
  # @api public
30
32
  def embed_async(text, cancellation_token = nil, timeout: nil)
31
33
  Phronomy::Runtime.instance.offload.submit(
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- VERSION = "0.19.0"
4
+ VERSION = "0.21.0"
5
5
  end
data/lib/phronomy.rb CHANGED
@@ -21,6 +21,12 @@ loader.inflector.inflect("before_llm_input" => "BeforeLLMInput")
21
21
  loader.collapse("#{__dir__}/phronomy/engine")
22
22
  # Loaded via require_relative before loader.setup; ignore to avoid Zeitwerk constant-name mismatch.
23
23
  loader.ignore("#{__dir__}/phronomy/ruby_llm_patches.rb")
24
+ # Persistence backend conformance tests are explicit test support. Keep them out
25
+ # of production eager-load so ordinary `require "phronomy"` never requires RSpec.
26
+ loader.ignore(
27
+ "#{__dir__}/phronomy/testing/persistence_contract.rb",
28
+ "#{__dir__}/phronomy/testing/persistence_contract"
29
+ )
24
30
  loader.setup
25
31
 
26
32
  require_relative "phronomy/version"
@@ -13,7 +13,8 @@
13
13
  # Target: mutation score >= 80% for each listed subject.
14
14
  # Baseline scores (as of initial run):
15
15
  # Phronomy::WorkflowContext 84.85%
16
- # Phronomy::Tool::Base 55.74%
16
+ # Phronomy::Agent::Context::Capability::Base 55.74%
17
+ # (public facade alias: Phronomy::Tool::Base; same Class object)
17
18
  #
18
19
  # Note: mutation testing is slow (~1-5 min per subject). Run locally or via
19
20
  # the nightly-mutation GitHub Actions workflow.
@@ -0,0 +1,36 @@
1
+ module Phronomy
2
+ module Agent
3
+ class Base
4
+ def self.model: (?String name) -> String?
5
+ def self.instructions: (?untyped text) { (untyped) -> untyped } -> untyped
6
+ | (?untyped text) -> untyped
7
+ def self.tools: (?Hash[Class, String?] definitions) -> untyped
8
+ def self.tool_aliases: () -> Hash[Class, String]
9
+ def self.provider: (?Symbol name) -> Symbol?
10
+ def self.temperature: (?Float value) -> Float?
11
+ def self.max_iterations: (?Integer value) -> Integer
12
+ def self.cache_instructions: (?bool enabled) -> bool?
13
+ def self.max_output_tokens: (?Integer value) -> Integer?
14
+ def self.context_window: (?Integer value) -> Integer?
15
+ def self.agent_definition: (?id: String?, ?version: Integer?) -> Hash[Symbol, untyped]
16
+ def self.create: (?agent_id: String, ?context: untyped, ?knowledge: Array[untyped], ?persistence: Persistence?, ?metadata: Hash[untyped, untyped]) -> instance
17
+ def self.load: (String agent_id, persistence: Persistence) -> instance
18
+ def self.live_for_execution: (String execution_id) -> instance
19
+
20
+ attr_reader agent_id: String
21
+ attr_reader persistence: Persistence
22
+
23
+ def invoke: (untyped input, **untyped) -> Hash[Symbol, untyped]
24
+ def invoke_async: (untyped input, **untyped) -> Task[Hash[Symbol, untyped]]
25
+ def stream: (untyped input, **untyped) { (untyped event) -> void } -> Hash[Symbol, untyped]
26
+ def stream_async: (untyped input, **untyped) -> Task[Hash[Symbol, untyped]]
27
+ def transcript: () -> Array[untyped]
28
+ def add_knowledge: (untyped content, ?metadata: Hash[untyped, untyped]) -> self
29
+ def clear_transcript!: () -> untyped
30
+ def clear_knowledge!: () -> untyped
31
+ def reset_context!: () -> untyped
32
+ def close!: () -> untyped
33
+ def purge!: () -> bool
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,50 @@
1
+ module Phronomy
2
+ module Filter
3
+ class Base
4
+ def call: (untyped value, **untyped context) -> untyped
5
+ end
6
+ end
7
+
8
+ module OutputParser
9
+ class Base
10
+ def parse: (String text) -> untyped
11
+ def invoke: (untyped input, ?config: Hash[untyped, untyped]) -> untyped
12
+ end
13
+ end
14
+
15
+ module Tracing
16
+ class Base
17
+ def start_span: (String name, **untyped attributes) -> untyped
18
+ def finish_span: (untyped span, ?output: untyped, ?usage: untyped, ?error: Exception?) -> untyped
19
+ def trace: [A] (String name, ?input: untyped, **untyped meta) { (untyped span) -> [A, untyped] } -> A
20
+ end
21
+ end
22
+
23
+ module VectorStore
24
+ class Base
25
+ def add: (id: String, embedding: Array[Float], ?metadata: Hash[untyped, untyped], ?cancellation_token: Concurrency::CancellationToken?) -> untyped
26
+ def search: (query_embedding: Array[Float], ?k: Integer, ?cancellation_token: Concurrency::CancellationToken?) -> Array[Hash[Symbol, untyped]]
27
+ def remove: (id: String) -> untyped
28
+ def clear: () -> untyped
29
+ def size: () -> Integer
30
+
31
+ def add_async: (id: String, embedding: Array[Float], ?metadata: Hash[untyped, untyped], ?cancellation_token: Concurrency::CancellationToken?, ?timeout: Numeric?) -> Task[untyped]
32
+ def search_async: (query_embedding: Array[Float], ?k: Integer, ?cancellation_token: Concurrency::CancellationToken?, ?timeout: Numeric?) -> Task[Array[Hash[Symbol, untyped]]]
33
+ def remove_async: (id: String, ?cancellation_token: Concurrency::CancellationToken?, ?timeout: Numeric?) -> Task[untyped]
34
+ def clear_async: (?cancellation_token: Concurrency::CancellationToken?, ?timeout: Numeric?) -> Task[untyped]
35
+ end
36
+
37
+ module Embeddings
38
+ class Base
39
+ def embed: (String text, ?Concurrency::CancellationToken? cancellation_token) -> Array[Float]
40
+ def embed_async: (String text, ?Concurrency::CancellationToken? cancellation_token, ?timeout: Numeric?) -> Task[Array[Float]]
41
+ end
42
+ end
43
+ end
44
+ module Testing
45
+ module PersistenceContract
46
+ SHARED_EXAMPLES: Array[String]
47
+ end
48
+ end
49
+
50
+ end
@@ -0,0 +1,11 @@
1
+ module Phronomy
2
+ module LLMAdapter
3
+ class Base
4
+ def complete: (untyped chat, String? message, ?config: Hash[untyped, untyped]) -> untyped
5
+ def stream: (untyped chat, String? message, ?config: Hash[untyped, untyped]) { (untyped chunk) -> void } -> untyped
6
+ end
7
+
8
+ class RubyLLM < Base
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,70 @@
1
+ module Phronomy
2
+ interface _ContentRepository
3
+ def put: (String bytes, canonicalization_version: untyped) -> String
4
+ def fetch: (String content_id) -> String
5
+ def exist?: (String content_id) -> bool
6
+ end
7
+
8
+ interface _AgentRepository
9
+ def create: (untyped root) -> untyped
10
+ def load: (String agent_id) -> untyped
11
+ def save: (String agent_id, expected_revision: Integer, root: untyped) -> untyped
12
+ def delete: (String agent_id) -> untyped
13
+ end
14
+
15
+ interface _JournalRepository
16
+ def append: (String agent_id, expected_position: Integer, records: Array[untyped]) -> Array[untyped]
17
+ def read: (String agent_id, ?after: Integer?, ?limit: Integer?) -> Array[untyped]
18
+ def head: (String agent_id) -> Integer
19
+ def delete: (String agent_id) -> untyped
20
+ end
21
+
22
+ interface _ExecutionRepository
23
+ def create_active: (untyped execution) -> untyped
24
+ def load: (String execution_id) -> untyped
25
+ def save: (String execution_id, expected_revision: Integer, execution: untyped) -> untyped
26
+ def list_active: (String agent_id) -> Array[untyped]
27
+ def delete: (String execution_id) -> untyped
28
+ def delete_for_agent: (String agent_id) -> untyped
29
+ def assert_idle!: (String agent_id) -> untyped
30
+ end
31
+
32
+ interface _WorkflowStateRepository
33
+ def load: (String thread_id) -> Hash[untyped, untyped]?
34
+ def save: (String thread_id, expected_revision: Integer?, snapshot: Hash[untyped, untyped]) -> untyped
35
+ def delete: (String thread_id, expected_revision: Integer) -> untyped
36
+ end
37
+
38
+ interface _PersistenceTransactionView
39
+ def contents: () -> _ContentRepository
40
+ def agents: () -> _AgentRepository
41
+ def journals: () -> _JournalRepository
42
+ def executions: () -> _ExecutionRepository
43
+ def workflow_states: () -> _WorkflowStateRepository
44
+ def assert_agent_watermark!: (agent_id: String, agent_revision: Integer, journal_position: Integer) -> true
45
+ end
46
+
47
+ class Persistence
48
+ class ConflictError < Phronomy::Error
49
+ end
50
+ class NotFoundError < Phronomy::Error
51
+ end
52
+ class UnsupportedBackendError < Phronomy::Error
53
+ end
54
+ class SerializationError < Phronomy::Error
55
+ end
56
+
57
+ REQUIRED_CAPABILITIES: Hash[Symbol, bool]
58
+
59
+ attr_reader contents: _ContentRepository
60
+ attr_reader agents: _AgentRepository
61
+ attr_reader journals: _JournalRepository
62
+ attr_reader executions: _ExecutionRepository
63
+ attr_reader workflow_states: _WorkflowStateRepository
64
+
65
+ def initialize: (contents: _ContentRepository, agents: _AgentRepository, journals: _JournalRepository, executions: _ExecutionRepository, workflow_states: _WorkflowStateRepository) -> void
66
+ def capabilities: () -> Hash[Symbol, bool]
67
+ def transaction: () { (_PersistenceTransactionView) -> untyped } -> untyped
68
+ def assert_agent_watermark!: (agent_id: String, agent_revision: Integer, journal_position: Integer) -> true
69
+ end
70
+ end