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.
- checksums.yaml +4 -4
- data/.mutant.yml +3 -1
- data/CHANGELOG.md +41 -11
- data/CONTRIBUTING.md +74 -6
- data/README.md +15 -6
- data/docs/decisions/010-cooperative-first-concurrency.md +86 -69
- data/docs/decisions/014-unified-persistence-durable-state.md +5 -0
- data/docs/decisions/015-tool-public-facade-and-rbs-boundary.md +184 -0
- data/docs/features.md +31 -2
- data/docs/getting-started.md +10 -1
- data/docs/migrations/0.19.md +12 -6
- data/docs/persistence-backends.md +504 -0
- data/docs/runtime-and-concurrency.md +110 -147
- data/lib/phronomy/agent/agent_execution.rb +29 -0
- data/lib/phronomy/agent/llm_call_record.rb +20 -0
- data/lib/phronomy/agent/tool_executor.rb +7 -3
- data/lib/phronomy/engine/concurrency/offload_pool.rb +142 -245
- data/lib/phronomy/engine/task.rb +50 -6
- data/lib/phronomy/invocation_context.rb +11 -52
- data/lib/phronomy/llm_adapter/base.rb +29 -32
- data/lib/phronomy/llm_adapter/ruby_llm.rb +13 -12
- data/lib/phronomy/llm_adapter.rb +10 -7
- data/lib/phronomy/output_parser/base.rb +5 -1
- data/lib/phronomy/persistence.rb +101 -7
- data/lib/phronomy/testing/persistence_contract/a_content_store.rb +50 -0
- data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +164 -0
- data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +215 -0
- data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +119 -0
- data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +99 -0
- data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +202 -0
- data/lib/phronomy/testing/persistence_contract.rb +41 -0
- data/lib/phronomy/tool/base.rb +15 -0
- data/lib/phronomy/tool.rb +11 -0
- data/lib/phronomy/vector_store/async_backend.rb +15 -38
- data/lib/phronomy/vector_store/base.rb +12 -13
- data/lib/phronomy/vector_store/embeddings/base.rb +11 -9
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy.rb +6 -0
- data/scripts/run_mutation.sh +2 -1
- data/sig/phronomy/agent.rbs +36 -0
- data/sig/phronomy/extensions.rbs +50 -0
- data/sig/phronomy/llm_adapter.rbs +11 -0
- data/sig/phronomy/persistence.rbs +70 -0
- data/sig/phronomy/runtime.rbs +43 -0
- data/sig/phronomy/tool.rbs +39 -0
- data/sig/phronomy/workflow.rbs +30 -0
- data/sig/phronomy.rbs +49 -1
- metadata +20 -3
- data/scripts/check_private_enforcement.rb +0 -93
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Phronomy
|
|
2
|
+
class Task[A]
|
|
3
|
+
attr_reader name: String?
|
|
4
|
+
attr_reader parent: Task[untyped]?
|
|
5
|
+
|
|
6
|
+
def status: () -> (:pending | :completed | :failed | :cancelled)
|
|
7
|
+
def done?: () -> bool
|
|
8
|
+
def alive?: () -> bool
|
|
9
|
+
def wait_result: (?timeout: Numeric?) -> A
|
|
10
|
+
def on_complete: () { (A?, Exception?) -> void } -> self
|
|
11
|
+
def map: [B] () { (A) -> B } -> Task[B]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module Concurrency
|
|
15
|
+
class CancellationToken
|
|
16
|
+
def self.timeout_after: (Numeric seconds) -> CancellationToken
|
|
17
|
+
def initialize: (?monotonic_deadline: Float?) -> void
|
|
18
|
+
def remaining_monotonic_seconds: () -> Float?
|
|
19
|
+
def on_cancel: () { () -> void } -> self
|
|
20
|
+
def cancel!: () -> self
|
|
21
|
+
def cancelled?: () -> bool
|
|
22
|
+
def raise_if_cancelled!: (?String message) -> nil
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module Phronomy
|
|
28
|
+
class InvocationContext
|
|
29
|
+
attr_reader thread_id: String?
|
|
30
|
+
attr_reader session_id: String?
|
|
31
|
+
attr_reader user_id: String?
|
|
32
|
+
attr_reader cancellation_token: Concurrency::CancellationToken?
|
|
33
|
+
attr_reader deadline: untyped
|
|
34
|
+
attr_reader tracer_span: untyped
|
|
35
|
+
attr_reader token_budget: Integer?
|
|
36
|
+
attr_reader approval_policy: untyped
|
|
37
|
+
attr_reader redaction_policy: untyped
|
|
38
|
+
attr_reader task_id: String?
|
|
39
|
+
attr_reader parent_task_id: String?
|
|
40
|
+
|
|
41
|
+
def initialize: (?thread_id: String?, ?session_id: String?, ?user_id: String?, ?cancellation_token: Concurrency::CancellationToken?, ?deadline: untyped, ?tracer_span: untyped, ?token_budget: Integer?, ?approval_policy: untyped, ?redaction_policy: untyped, ?task_id: String?, ?parent_task_id: String?) -> void
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Phronomy
|
|
2
|
+
module Tool
|
|
3
|
+
class Base
|
|
4
|
+
def self.tool_name: (?untyped value) -> String?
|
|
5
|
+
def self.description: (?String text) -> String?
|
|
6
|
+
def self.desc: (?String text) -> String?
|
|
7
|
+
def self.parameters: () -> Hash[untyped, untyped]
|
|
8
|
+
def self.params_schema_definition: () -> untyped
|
|
9
|
+
def self.provider_params: () -> Hash[untyped, untyped]
|
|
10
|
+
def self.param: (untyped name, ?enum: untyped, ?properties: untyped, **untyped options) -> untyped
|
|
11
|
+
def self.execution_mode: (?(:cooperative | :offloaded) value) -> (:cooperative | :offloaded)
|
|
12
|
+
def self.on_error: (?(:raise | :suppress) behavior) -> (:raise | :suppress)
|
|
13
|
+
def self.on_schema_error: (?untyped behavior) -> untyped
|
|
14
|
+
def self.requires_approval: (?untyped value) { () -> untyped } -> untyped
|
|
15
|
+
| (?untyped value) -> untyped
|
|
16
|
+
def self.approval_facts: () { (untyped, untyped) -> untyped } -> untyped
|
|
17
|
+
| () -> untyped
|
|
18
|
+
def self.redact_params: (*untyped names) -> Array[Symbol]
|
|
19
|
+
def self.max_result_size: (?untyped value) -> untyped
|
|
20
|
+
|
|
21
|
+
def name: () -> String
|
|
22
|
+
def params_schema: () -> untyped
|
|
23
|
+
def call: (Hash[untyped, untyped] args, ?cancellation_token: Concurrency::CancellationToken?) -> untyped
|
|
24
|
+
def call_async: (Hash[untyped, untyped] args, ?cancellation_token: Concurrency::CancellationToken?, ?config: Hash[untyped, untyped]) -> Task[untyped]
|
|
25
|
+
def requires_approval?: () -> untyped
|
|
26
|
+
def tool_origin: () -> Symbol
|
|
27
|
+
def approval_metadata: () -> Hash[untyped, untyped]
|
|
28
|
+
def execute: (**untyped args) -> untyped
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
module Agent
|
|
33
|
+
module Context
|
|
34
|
+
module Capability
|
|
35
|
+
class Base = ::Phronomy::Tool::Base
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Phronomy
|
|
2
|
+
module WorkflowContext
|
|
3
|
+
def phase: () -> Symbol
|
|
4
|
+
def halted?: () -> bool
|
|
5
|
+
def thread_id: () -> String?
|
|
6
|
+
def merge: (Hash[Symbol, untyped] updates) -> self
|
|
7
|
+
def to_h: () -> Hash[Symbol, untyped]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class Workflow
|
|
11
|
+
def self.define: (Class context_class, ?persistence: Persistence?) { () -> void } -> Workflow
|
|
12
|
+
|
|
13
|
+
def invoke: (untyped input, ?config: Hash[untyped, untyped], ?invocation_context: untyped) -> untyped
|
|
14
|
+
def invoke_async: (untyped input, ?config: Hash[untyped, untyped], ?invocation_context: untyped) -> Task[untyped]
|
|
15
|
+
def stream: (untyped input, ?config: Hash[untyped, untyped], ?invocation_context: untyped) { (untyped) -> void } -> untyped
|
|
16
|
+
def resume: (state: untyped, ?input: untyped) -> untyped
|
|
17
|
+
def send_event: (state: untyped, event: Symbol, ?input: untyped) -> untyped
|
|
18
|
+
def signal: (thread_id: String, event: Symbol, ?payload: untyped) -> bool
|
|
19
|
+
|
|
20
|
+
class Builder
|
|
21
|
+
def initial: (Symbol state_name) -> untyped
|
|
22
|
+
def state: (Symbol name, ?action: untyped) -> untyped
|
|
23
|
+
def entry: (Symbol name, untyped callable) -> untyped
|
|
24
|
+
def exit: (Symbol name, untyped callable) -> untyped
|
|
25
|
+
def wait_state: (Symbol name) -> untyped
|
|
26
|
+
def transition: (from: Symbol, to: Symbol, ?guard: untyped, ?on: Symbol?, ?action: untyped) -> untyped
|
|
27
|
+
def build: () -> Workflow
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/sig/phronomy.rbs
CHANGED
|
@@ -1,4 +1,52 @@
|
|
|
1
1
|
module Phronomy
|
|
2
2
|
VERSION: String
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
class ParseError < Error
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class RecursionLimitError < Error
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class ToolError < Error
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class TimeoutError < Error
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class ConfigurationError < Error
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class TransportError < Error
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class RateLimitError < TransportError
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class AuthenticationError < TransportError
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class ContextLengthError < Error
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class CancellationError < Error
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class EventLoopReentrancyError < Error
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class PoolShutdownError < Error
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class BackpressureError < Error
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class Configuration
|
|
47
|
+
attr_accessor llm_adapter: LLMAdapter::Base
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.configuration: () -> Configuration
|
|
51
|
+
def self.configure: () { (Configuration) -> void } -> void
|
|
4
52
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: phronomy
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.21.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Raizo T.C.S
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ruby_llm
|
|
@@ -120,12 +120,14 @@ files:
|
|
|
120
120
|
- docs/decisions/012-canonical-execution-log-and-context-policy.md
|
|
121
121
|
- docs/decisions/013-journal-backed-knowledge-as-context-candidates.md
|
|
122
122
|
- docs/decisions/014-unified-persistence-durable-state.md
|
|
123
|
+
- docs/decisions/015-tool-public-facade-and-rbs-boundary.md
|
|
123
124
|
- docs/features.md
|
|
124
125
|
- docs/getting-started.md
|
|
125
126
|
- docs/mcp-client.md
|
|
126
127
|
- docs/migrations/0.15.md
|
|
127
128
|
- docs/migrations/0.16.md
|
|
128
129
|
- docs/migrations/0.19.md
|
|
130
|
+
- docs/persistence-backends.md
|
|
129
131
|
- docs/runtime-and-concurrency.md
|
|
130
132
|
- examples/workflows/agent_event_mapping.rb
|
|
131
133
|
- examples/workflows/generic_task_event_mapping.rb
|
|
@@ -250,7 +252,16 @@ files:
|
|
|
250
252
|
- lib/phronomy/testing/eval/scorer/includes_scorer.rb
|
|
251
253
|
- lib/phronomy/testing/eval/scorer/llm_judge.rb
|
|
252
254
|
- lib/phronomy/testing/fake_clock.rb
|
|
255
|
+
- lib/phronomy/testing/persistence_contract.rb
|
|
256
|
+
- lib/phronomy/testing/persistence_contract/a_content_store.rb
|
|
257
|
+
- lib/phronomy/testing/persistence_contract/a_journal_repository.rb
|
|
258
|
+
- lib/phronomy/testing/persistence_contract/a_persistence_backend.rb
|
|
259
|
+
- lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb
|
|
260
|
+
- lib/phronomy/testing/persistence_contract/an_agent_repository.rb
|
|
261
|
+
- lib/phronomy/testing/persistence_contract/an_execution_repository.rb
|
|
253
262
|
- lib/phronomy/token_usage.rb
|
|
263
|
+
- lib/phronomy/tool.rb
|
|
264
|
+
- lib/phronomy/tool/base.rb
|
|
254
265
|
- lib/phronomy/tools/agent.rb
|
|
255
266
|
- lib/phronomy/tools/mcp.rb
|
|
256
267
|
- lib/phronomy/tools/vector_search.rb
|
|
@@ -283,7 +294,6 @@ files:
|
|
|
283
294
|
- scripts/add_to_h_unnamed_doubles.rb
|
|
284
295
|
- scripts/api_snapshot.rb
|
|
285
296
|
- scripts/check_api_annotations.rb
|
|
286
|
-
- scripts/check_private_enforcement.rb
|
|
287
297
|
- scripts/check_readme_ruby.rb
|
|
288
298
|
- scripts/check_readme_runnable.rb
|
|
289
299
|
- scripts/migrate_spec_agent_definition.rb
|
|
@@ -291,6 +301,13 @@ files:
|
|
|
291
301
|
- scripts/migrate_spec_inline_pass3.rb
|
|
292
302
|
- scripts/run_mutation.sh
|
|
293
303
|
- sig/phronomy.rbs
|
|
304
|
+
- sig/phronomy/agent.rbs
|
|
305
|
+
- sig/phronomy/extensions.rbs
|
|
306
|
+
- sig/phronomy/llm_adapter.rbs
|
|
307
|
+
- sig/phronomy/persistence.rbs
|
|
308
|
+
- sig/phronomy/runtime.rbs
|
|
309
|
+
- sig/phronomy/tool.rbs
|
|
310
|
+
- sig/phronomy/workflow.rbs
|
|
294
311
|
homepage: https://github.com/Raizo-TCS/phronomy
|
|
295
312
|
licenses:
|
|
296
313
|
- MIT
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# check_private_enforcement.rb
|
|
5
|
-
#
|
|
6
|
-
# Verifies that every instance method annotated @api private in lib/ is
|
|
7
|
-
# actually non-public at the Ruby level (i.e., NOT in Module#public_instance_methods).
|
|
8
|
-
#
|
|
9
|
-
# Class methods (def self.xxx) are excluded from this check because their
|
|
10
|
-
# visibility is managed separately on the singleton class and rarely causes
|
|
11
|
-
# accidental public exposure to consumers.
|
|
12
|
-
#
|
|
13
|
-
# Usage (run from the phronomy/ repository root):
|
|
14
|
-
# bundle exec ruby scripts/check_private_enforcement.rb
|
|
15
|
-
#
|
|
16
|
-
# Exit codes:
|
|
17
|
-
# 0 — all @api private instance methods are non-public (or have no Ruby def)
|
|
18
|
-
# 1 — one or more @api private instance methods are exposed as public
|
|
19
|
-
|
|
20
|
-
require "bundler/setup"
|
|
21
|
-
require_relative "../lib/phronomy"
|
|
22
|
-
|
|
23
|
-
lib_dir = File.expand_path("../lib", __dir__)
|
|
24
|
-
|
|
25
|
-
unless File.directory?(lib_dir)
|
|
26
|
-
warn "ERROR: lib directory not found at #{lib_dir}"
|
|
27
|
-
exit 1
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# Step 1: Collect instance methods annotated @api private via static analysis.
|
|
31
|
-
api_private_entries = []
|
|
32
|
-
|
|
33
|
-
Dir.glob(File.join(lib_dir, "**", "*.rb")).sort.each do |file|
|
|
34
|
-
lines = File.readlines(file)
|
|
35
|
-
|
|
36
|
-
lines.each_with_index do |line, i|
|
|
37
|
-
next unless line.match?(/^\s*#\s*@api\s+private\s*$/)
|
|
38
|
-
|
|
39
|
-
# Advance past any further comment or blank lines to reach the def.
|
|
40
|
-
j = i + 1
|
|
41
|
-
j += 1 while j < lines.size && lines[j].match?(/^\s*(#|$)/)
|
|
42
|
-
next unless j < lines.size
|
|
43
|
-
|
|
44
|
-
# Skip class-level methods — they live on the singleton class, not as
|
|
45
|
-
# public instance methods accessible to consumers.
|
|
46
|
-
next if lines[j].match?(/def\s+self\./)
|
|
47
|
-
|
|
48
|
-
# Match both plain def and "private def".
|
|
49
|
-
m = lines[j].match(/^\s*(?:private\s+)?def\s+(\w+[!?=]?)/)
|
|
50
|
-
next unless m
|
|
51
|
-
|
|
52
|
-
rel_path = file.sub("#{lib_dir}/../", "")
|
|
53
|
-
api_private_entries << {name: m[1].to_sym, file: rel_path, line: j + 1}
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
if api_private_entries.empty?
|
|
58
|
-
puts "No @api private instance methods found."
|
|
59
|
-
exit 0
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# Step 2: Build a map of publicly exposed instance methods across all
|
|
63
|
-
# Phronomy-namespaced modules/classes (own methods only, no inheritance).
|
|
64
|
-
all_phronomy_modules = ObjectSpace.each_object(Module).select do |mod|
|
|
65
|
-
mod.name&.start_with?("Phronomy")
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
public_exposure_map = {}
|
|
69
|
-
all_phronomy_modules.each do |mod|
|
|
70
|
-
mod.public_instance_methods(false).each do |meth|
|
|
71
|
-
(public_exposure_map[meth] ||= []) << mod.name
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Step 3: Report violations — @api private methods that are still public.
|
|
76
|
-
errors = []
|
|
77
|
-
|
|
78
|
-
api_private_entries.each do |entry|
|
|
79
|
-
exposing_modules = public_exposure_map[entry[:name]]
|
|
80
|
-
next unless exposing_modules
|
|
81
|
-
|
|
82
|
-
errors << "#{entry[:file]}:#{entry[:line]} def #{entry[:name]}" \
|
|
83
|
-
" (annotated @api private but public in: #{exposing_modules.join(", ")})"
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
if errors.empty?
|
|
87
|
-
puts "OK: all #{api_private_entries.size} @api private instance methods are non-public."
|
|
88
|
-
exit 0
|
|
89
|
-
else
|
|
90
|
-
warn "ERROR: #{errors.size} @api private instance method(s) are exposed as public:"
|
|
91
|
-
errors.each { |e| warn " #{e}" }
|
|
92
|
-
exit 1
|
|
93
|
-
end
|