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,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
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
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.19.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-15 00:00:00.000000000 Z
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