phronomy 0.32.0 → 0.34.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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +139 -0
  3. data/CONTRIBUTING.md +5 -5
  4. data/README.md +20 -1
  5. data/RELEASE_CHECKLIST.md +7 -1
  6. data/SECURITY.md +1 -1
  7. data/VERIFY.sh +1 -9
  8. data/docs/architecture/error-api-review.md +6 -2
  9. data/docs/architecture/structured-output-and-scope.md +8 -8
  10. data/docs/architecture/trust-acceptance-followup.md +15 -0
  11. data/docs/context-policy-budgeting.md +108 -0
  12. data/docs/decisions/016-semantic-multi-agent-handoff.md +6 -5
  13. data/docs/examples/shared_state_custom_sharing.rb +145 -0
  14. data/docs/features.md +4 -2
  15. data/docs/mcp-client.md +4 -2
  16. data/docs/migrations/cleanup-audit.md +89 -0
  17. data/docs/migrations/legacy-format-removal.md +55 -0
  18. data/docs/migrations/r8-unit6.md +4 -2
  19. data/docs/migrations/shared-state-multi-agent.md +5 -0
  20. data/docs/migrations/vector-async-clients.md +6 -1
  21. data/docs/runtime-and-concurrency.md +36 -0
  22. data/docs/shared-state-sharing.md +233 -0
  23. data/lib/phronomy/agent/base.rb +53 -0
  24. data/lib/phronomy/agent/execution/agent_invocation.rb +0 -4
  25. data/lib/phronomy/agent/execution/execution_coordinator.rb +11 -0
  26. data/lib/phronomy/agent/handoff/transfer_projection.rb +0 -9
  27. data/lib/phronomy/agent/journal/journal_record.rb +2 -2
  28. data/lib/phronomy/agent/lifecycle/hosted_invocation.rb +2 -5
  29. data/lib/phronomy/agent/recovery/recovery_rules.rb +0 -12
  30. data/lib/phronomy/agent/store.rb +3 -8
  31. data/lib/phronomy/agent/tool_execution/tool_approval_request.rb +3 -2
  32. data/lib/phronomy/agent/tool_execution/tool_invocation.rb +9 -36
  33. data/lib/phronomy/context/context_policy.rb +13 -8
  34. data/lib/phronomy/context/context_policy_input.rb +26 -0
  35. data/lib/phronomy/context/llm_input_manifest.rb +3 -3
  36. data/lib/phronomy/engine/runtime/shutdown_result.rb +0 -5
  37. data/lib/phronomy/execution/execution.rb +0 -11
  38. data/lib/phronomy/multi_agent/shared_state/binding.rb +44 -0
  39. data/lib/phronomy/multi_agent/shared_state/member_lifecycle_error.rb +32 -0
  40. data/lib/phronomy/multi_agent/shared_state.rb +152 -10
  41. data/lib/phronomy/tool/base.rb +69 -3
  42. data/lib/phronomy/tool/schema.rb +23 -11
  43. data/lib/phronomy/version.rb +1 -1
  44. data/scripts/api_snapshot.rb +6 -3
  45. data/sig/phronomy/agent.rbs +1 -0
  46. data/sig/phronomy/context.rbs +3 -0
  47. data/sig/phronomy/multi_agent.rbs +26 -2
  48. data/sig/phronomy/tool.rbs +1 -0
  49. metadata +9 -11
  50. data/lib/phronomy/execution/scheduler_reentrancy_error.rb +0 -9
  51. data/lib/phronomy/persistence/migration/initial_format_migration.rb +0 -226
  52. data/scripts/add_to_h_to_token_doubles.rb +0 -33
  53. data/scripts/add_to_h_unnamed_doubles.rb +0 -27
  54. data/scripts/migrate_generator_verifier_result.rb +0 -18
  55. data/scripts/migrate_spec_agent_definition.rb +0 -108
  56. data/scripts/migrate_spec_agent_definition_pass2.rb +0 -53
  57. data/scripts/migrate_spec_inline_pass3.rb +0 -24
  58. data/sig/phronomy/persistence/migration.rbs +0 -13
@@ -1,108 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Migrates spec files to add agent_definition to Agent::Base subclasses.
5
- # - Named classes: inserts after the class declaration line
6
- # - Anonymous blocks (do...end): inserts after the opening `do`
7
- # - Inline anonymous ({ ... }): expands to multi-line with agent_definition
8
- # Safe: skips classes that already have agent_definition
9
-
10
- require "fileutils"
11
-
12
- SPEC_DIR = File.expand_path("../spec", __dir__)
13
- AGENT_BASE_PATTERN = /Phronomy::Agent::Base/
14
-
15
- def snake_case(class_name)
16
- # Remove trailing Agent/Tool suffixes, snake_case the name
17
- name = class_name.split("::").last
18
- name.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
19
- .gsub(/([a-z\d])([A-Z])/, '\1_\2')
20
- .downcase
21
- end
22
-
23
- counter = 0
24
- files_modified = 0
25
-
26
- Dir.glob("#{SPEC_DIR}/**/*.rb").sort.each do |file|
27
- lines = File.readlines(file, chomp: false)
28
- new_lines = []
29
- modified = false
30
- i = 0
31
-
32
- while i < lines.length
33
- line = lines[i]
34
-
35
- # ── Named class: class FooAgent < Phronomy::Agent::Base ──────────────────
36
- if line =~ /^(\s*)class\s+(\w+)\s*<\s*(?:::)?Phronomy::Agent::Base\s*$/
37
- indent = $1
38
- class_name = $2
39
- id = snake_case(class_name).sub(/_agent$/, "-agent").tr("_", "-")
40
- id = "#{id}-agent" unless id.end_with?("-agent")
41
- new_lines << line
42
- i += 1
43
- # Check if agent_definition already present in the class body
44
- look_ahead = lines[i..]&.take(10) || []
45
- unless look_ahead.any? { |l| l =~ /agent_definition/ }
46
- new_lines << "#{indent} agent_definition id: #{id.inspect}, version: 1\n"
47
- counter += 1
48
- modified = true
49
- end
50
- next
51
- end
52
-
53
- # ── Anonymous block (do...end): Class.new(Phronomy::Agent::Base) do ─────
54
- if line =~ /^(\s*)([\w.]*\s*=\s*)?Class\.new\((?:::)?Phronomy::Agent::Base\)\s+do\s*(\|\w+\|)?\s*$/
55
- indent = $1
56
- # Check if agent_definition already present
57
- look_ahead = lines[i + 1..]&.take(10) || []
58
- new_lines << line
59
- i += 1
60
- unless look_ahead.any? { |l| l =~ /agent_definition/ }
61
- # Derive indentation from the `do` block body (first non-blank line)
62
- inner = lines[i..]&.take(5)&.find { |l| l.strip.length > 0 } || ""
63
- inner_indent = inner.match(/^(\s*)/)[1]
64
- inner_indent = "#{indent} " if inner_indent.strip.length > 0 && inner_indent.length < indent.length + 2
65
- new_lines << "#{inner_indent}agent_definition id: \"test-agent-#{counter += 1}\", version: 1\n"
66
- modified = true
67
- end
68
- next
69
- end
70
-
71
- # ── Inline anonymous: Class.new(Phronomy::Agent::Base) { ... } ───────────
72
- if line =~ /^(\s*)((?:[\w.]*\s*=\s*)?)Class\.new\((?:::)?Phronomy::Agent::Base\)\s*\{(.+)\}\s*$/
73
- indent = $1
74
- assignment = $2
75
- body = $3
76
- # Skip if agent_definition already present
77
- if /agent_definition/.match?(body)
78
- new_lines << line
79
- else
80
- id = counter += 1
81
- definition_line = "agent_definition id: \"test-agent-#{id}\", version: 1;"
82
- new_lines << "#{indent}#{assignment}Class.new(Phronomy::Agent::Base) do\n"
83
- new_lines << "#{indent} #{definition_line}\n"
84
- # Rewrite the original body lines, stripping leading spaces
85
- body.strip.split(";").each do |stmt|
86
- stmt = stmt.strip
87
- next if stmt.empty?
88
- new_lines << "#{indent} #{stmt}\n"
89
- end
90
- new_lines << "#{indent}end\n"
91
- modified = true
92
- end
93
- i += 1
94
- next
95
- end
96
-
97
- new_lines << line
98
- i += 1
99
- end
100
-
101
- if modified
102
- File.write(file, new_lines.join)
103
- files_modified += 1
104
- puts " patched: #{file.sub("#{SPEC_DIR}/", "spec/")}"
105
- end
106
- end
107
-
108
- puts "\nDone: #{counter} agent_definition insertions in #{files_modified} files."
@@ -1,53 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Second pass: handles patterns missed by the first script
5
- # - Class.new(Phronomy::Agent::Base) { ... }.new (inline + .new suffix)
6
- # - Class.new(Phronomy::Agent::Base).new (no block + .new)
7
- # - { Class.new(Phronomy::Agent::Base) } (no block, inside outer { })
8
-
9
- require "fileutils"
10
-
11
- SPEC_DIR = File.expand_path("../spec", __dir__)
12
- counter = 200
13
-
14
- files_modified = 0
15
-
16
- Dir.glob("#{SPEC_DIR}/**/*.rb").sort.each do |file|
17
- content = File.read(file)
18
- new_content = content.dup
19
-
20
- # Pattern A: Class.new(Phronomy::Agent::Base) { body }.new
21
- new_content = new_content.gsub(
22
- /Class\.new\(Phronomy::Agent::Base\) \{([^}]*)\}\.new/
23
- ) do
24
- body = $1
25
- next $& if body.include?("agent_definition")
26
- counter += 1
27
- "Class.new(Phronomy::Agent::Base) { agent_definition id: \"test-agent-#{counter}\", version: 1;#{body}}.new"
28
- end
29
-
30
- # Pattern B: Class.new(Phronomy::Agent::Base).new (no block, chained .new)
31
- new_content = new_content.gsub(
32
- /Class\.new\(Phronomy::Agent::Base\)\.new\b/
33
- ) do
34
- counter += 1
35
- "Class.new(Phronomy::Agent::Base) { agent_definition id: \"test-agent-#{counter}\", version: 1 }.new"
36
- end
37
-
38
- # Pattern C: { Class.new(Phronomy::Agent::Base) } (no block, inside outer block)
39
- new_content = new_content.gsub(
40
- "{ Class.new(Phronomy::Agent::Base) }"
41
- ) do
42
- counter += 1
43
- "{ Class.new(Phronomy::Agent::Base) { agent_definition id: \"test-agent-#{counter}\", version: 1 } }"
44
- end
45
-
46
- if new_content != content
47
- File.write(file, new_content)
48
- files_modified += 1
49
- puts " patched: #{file.sub("#{SPEC_DIR}/", "spec/")}"
50
- end
51
- end
52
-
53
- puts "\nSecond pass done: #{files_modified} files modified."
@@ -1,24 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Adds agent_definition to single-line inline anonymous blocks like:
5
- # Class.new(Phronomy::Agent::Base) { model "test-model" }
6
-
7
- counter = 300
8
- files = Dir.glob("spec/**/*.rb").sort
9
-
10
- files.each do |f|
11
- content = File.read(f)
12
- modified = content.gsub(
13
- /Class\.new\(Phronomy::Agent::Base\) \{ ([^{}]+) \}/
14
- ) do
15
- body = $1
16
- next $& if body.include?("agent_definition")
17
- counter += 1
18
- "Class.new(Phronomy::Agent::Base) { agent_definition id: \"test-agent-#{counter}\", version: 1; #{body} }"
19
- end
20
- if modified != content
21
- File.write(f, modified)
22
- puts " patched: #{f}"
23
- end
24
- end
@@ -1,13 +0,0 @@
1
- module Phronomy
2
- class Persistence
3
- module Migration
4
- module InitialFormatMigration
5
- def self.agent_root: (Hash[untyped, untyped] hash) -> Storage::DurableRecord
6
- def self.agent_execution: (Hash[untyped, untyped] hash) -> Storage::DurableRecord
7
- def self.journal_record: (Hash[untyped, untyped] hash) -> Storage::DurableRecord
8
- def self.workflow_state: (workflow_instance_id: String, revision: Integer, snapshot: Hash[untyped, untyped]) -> Storage::DurableRecord
9
- def self.llm_input_manifest: (Hash[untyped, untyped] hash) -> Hash[String, untyped]
10
- end
11
- end
12
- end
13
- end