phronomy 0.32.0 → 0.33.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/CHANGELOG.md +77 -0
- data/README.md +7 -1
- data/RELEASE_CHECKLIST.md +4 -0
- data/docs/architecture/structured-output-and-scope.md +5 -4
- data/docs/architecture/trust-acceptance-followup.md +15 -0
- data/docs/context-policy-budgeting.md +108 -0
- data/docs/examples/shared_state_custom_sharing.rb +145 -0
- data/docs/features.md +2 -0
- data/docs/mcp-client.md +4 -2
- data/docs/migrations/shared-state-multi-agent.md +5 -0
- data/docs/runtime-and-concurrency.md +36 -0
- data/docs/shared-state-sharing.md +223 -0
- data/lib/phronomy/agent/base.rb +53 -0
- data/lib/phronomy/agent/execution/execution_coordinator.rb +11 -0
- data/lib/phronomy/agent/lifecycle/hosted_invocation.rb +2 -5
- data/lib/phronomy/agent/tool_execution/tool_approval_request.rb +3 -2
- data/lib/phronomy/agent/tool_execution/tool_invocation.rb +12 -36
- data/lib/phronomy/context/context_policy.rb +11 -2
- data/lib/phronomy/context/context_policy_input.rb +26 -0
- data/lib/phronomy/multi_agent/shared_state/binding.rb +44 -0
- data/lib/phronomy/multi_agent/shared_state/member_lifecycle_error.rb +32 -0
- data/lib/phronomy/multi_agent/shared_state.rb +147 -2
- data/lib/phronomy/tool/base.rb +69 -3
- data/lib/phronomy/tool/schema.rb +23 -11
- data/lib/phronomy/version.rb +1 -1
- data/scripts/api_snapshot.rb +6 -3
- data/sig/phronomy/agent.rbs +1 -0
- data/sig/phronomy/context.rbs +3 -0
- data/sig/phronomy/multi_agent.rbs +26 -2
- data/sig/phronomy/tool.rbs +1 -0
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e696f701b22d91c4ccb8f566e28274b73e9fd69704a673e18a687a1d377b4583
|
|
4
|
+
data.tar.gz: 7a63749fb58c70fb6c74a843a601bb48401ffe35fa02de03a63fa9f8d6320552
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: af029ecc2491d2912c48fef847f8501ce255040759bb5d8edba3949d2bea0352b2a6a89e731d70493570c5b72d26e094ed03d80aff412f6805af21a0da588219
|
|
7
|
+
data.tar.gz: 529ece7bc74b4096d332e9f39a3097933f1441797f5dc22b5bb9ec9d5b39fde2c42709d2b53e83b99a3e42ec6e2e9a227920f10371e6ac65772241cb54d0abc7
|
data/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,83 @@ Release history for 0.14.0 and earlier is archived in
|
|
|
12
12
|
|
|
13
13
|
## [Unreleased]
|
|
14
14
|
|
|
15
|
+
## [0.33.0] - 2026-11-14
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Add public `ContextPolicyInput#input_token_limit` and
|
|
20
|
+
`#with_input_token_limit(limit)` for immutable, non-expanding selection limits,
|
|
21
|
+
plus protected `ContextPolicy#estimate_tokens(content)` using the same
|
|
22
|
+
canonical estimator as generated Policy items. Applications can reserve input
|
|
23
|
+
capacity while retaining DefaultPolicy selection without constructing private
|
|
24
|
+
budget objects or calling private token-estimation helpers. These operations
|
|
25
|
+
do not change model capabilities, generation settings or final validation.
|
|
26
|
+
- Add `Agent::Base.with_tools(definitions, id:, version:)` to derive an Agent
|
|
27
|
+
class with a complete replacement Tool map and explicit definition identity.
|
|
28
|
+
The factory preserves Phronomy authoring settings and independently copies
|
|
29
|
+
filter lists; configured callable instances remain shared. Custom SharedState
|
|
30
|
+
uses this public operation after merging member and application Tools. General
|
|
31
|
+
Agent subclass inheritance remains unchanged.
|
|
32
|
+
- Add `MultiAgent::SharedState.sharing(provider, id:, version:)` and the public
|
|
33
|
+
`SharedState::Binding` value. A synchronous provider opens one application-owned
|
|
34
|
+
store for an invocation and supplies Tool definitions for each member/cycle.
|
|
35
|
+
Applications define their own records, search arguments and cleanup without
|
|
36
|
+
overriding SharedState private methods. Custom sharing requires an explicit
|
|
37
|
+
aggregation block and does not inject the default findings Tools or append
|
|
38
|
+
stored records to member prompts.
|
|
39
|
+
- Include the sharing provider's stable ID and semantic version in custom
|
|
40
|
+
generated Agent definition identity. Default SharedState retains its existing
|
|
41
|
+
built-in Tools, prompts and instrumentation revision 1. The extension adds no
|
|
42
|
+
database adapter, ContextPolicy implementation or durable sharing lifecycle.
|
|
43
|
+
- Release terminal custom-sharing members through the public Agent lifecycle
|
|
44
|
+
operation. Normal Agent approval waits keep the invocation and provider open.
|
|
45
|
+
`SharedState::MemberLifecycleError` reports failed release or a custom
|
|
46
|
+
`invoke` override returning a suspended result prematurely, with the Agent and
|
|
47
|
+
available result/error details. It does not automatically answer approvals or
|
|
48
|
+
release a pending execution; application providers retain responsibility for
|
|
49
|
+
resource cleanup and recovery policy.
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
|
|
53
|
+
- Reuse the immutable argument schema prepared by an explicit `parameters`
|
|
54
|
+
declaration. Each Tool instance compiles its own validator on first use.
|
|
55
|
+
|
|
56
|
+
### Fixed
|
|
57
|
+
|
|
58
|
+
- Build each human-facing Tool approval item from the Tool's public
|
|
59
|
+
`approval_display(arguments:, facts:)` result. A Tool without this operation
|
|
60
|
+
contributes empty argument and fact maps to new approval requests. Tool
|
|
61
|
+
execution and policy evaluation still receive their original inputs.
|
|
62
|
+
- Apply the default approval display's sensitive-value check to values inside
|
|
63
|
+
a Tool's redacted hash or array arguments, including copies used as fact keys.
|
|
64
|
+
|
|
65
|
+
### Security
|
|
66
|
+
|
|
67
|
+
- Use patched ActiveRecord 8.0 development dependencies, including ActiveSupport
|
|
68
|
+
8.0.5.2 or later in the 8.0 series. Remove unused ActiveJob and SQLite3
|
|
69
|
+
development dependencies; the examples retain the dependencies they use.
|
|
70
|
+
The published gem's runtime dependencies are unchanged.
|
|
71
|
+
- Provide Bundler Audit in the development bundle and check the complete
|
|
72
|
+
dependency set in pull-request CI and immediately before release publication.
|
|
73
|
+
|
|
74
|
+
### Compatibility and verification
|
|
75
|
+
|
|
76
|
+
- The new SharedState sharing, Agent Tool composition and Context budgeting APIs
|
|
77
|
+
require 0.33.0 or later and remain Experimental. Default sharing and ordinary
|
|
78
|
+
Agent subclass inheritance retain their existing behavior. Custom sharing is
|
|
79
|
+
synchronous and invocation-scoped; it does not add durable sharing recovery.
|
|
80
|
+
- Approval display is selected by each Tool. Custom Tool implementations without
|
|
81
|
+
`approval_display` expose empty display maps; execution and policy evaluation
|
|
82
|
+
still use the original arguments. Review custom display overrides for safe
|
|
83
|
+
human-facing fields when upgrading.
|
|
84
|
+
- Observed LM Studio/gpt-oss PEG-format failures remain unresolved.
|
|
85
|
+
The existing adapter identifies known errors and bounds retries; this release
|
|
86
|
+
does not claim to fix their root cause or establish full real-LLM acceptance.
|
|
87
|
+
The latest full examples verification recorded 102 passed, 1 failed and no
|
|
88
|
+
skips (Example 22: HTTP 400). A separate Qwen Example 22 run completed all three
|
|
89
|
+
cycles, but took 2,220 seconds and does not replace the full verifier result
|
|
90
|
+
or satisfy its 240-second per-example limit.
|
|
91
|
+
|
|
15
92
|
## [0.32.0] - 2026-11-03
|
|
16
93
|
|
|
17
94
|
### Added
|
data/README.md
CHANGED
|
@@ -37,13 +37,19 @@ tracking `main` directly.
|
|
|
37
37
|
|
|
38
38
|
See [Features and Application Programming Interface (API) stability](docs/features.md) for the full feature matrix.
|
|
39
39
|
|
|
40
|
-
The following table describes the application APIs available in v0.
|
|
40
|
+
The following table describes the application APIs available in v0.33.0.
|
|
41
|
+
The new sharing, Tool composition and Context budgeting operations remain
|
|
42
|
+
Experimental; their introduction version is shown below.
|
|
41
43
|
Worker hosting and completed parent/child release require v0.32.0 or later.
|
|
42
44
|
Review the [0.31 migration notes](docs/migrations/0.31.md) before upgrading
|
|
43
45
|
persistent applications or custom Agent Tool subclasses.
|
|
44
46
|
|
|
45
47
|
| API | Stability | Contract |
|
|
46
48
|
|---|---|---|
|
|
49
|
+
| `Context::ContextPolicyInput#input_token_limit / #with_input_token_limit` and protected `ContextPolicy#estimate_tokens` | Experimental (since v0.33.0) | Inspect and constrain selection budgets and estimate candidate content through Context-owned operations. [Policy budgeting](docs/context-policy-budgeting.md) |
|
|
50
|
+
| `Agent::Base.with_tools(definitions, id:, version:)` | Experimental (since v0.33.0) | Derive an Agent class with a complete replacement Tool map and explicit definition identity while preserving Phronomy authoring settings and filters. [Tool composition](docs/shared-state-sharing.md#deriving-an-agent-with-a-complete-tool-set) |
|
|
51
|
+
| `MultiAgent::SharedState.sharing(provider, id:, version:)`, `SharedState::Binding` and `SharedState::MemberLifecycleError` | Experimental (since v0.33.0) | Connect application-owned shared data and Tools to sequential coordination through a synchronous invocation-scoped provider. [Custom sharing contract and example](docs/shared-state-sharing.md) |
|
|
52
|
+
| `Tool::Base#approval_display(arguments:, facts:)` | Beta (since v0.33.0) | The Tool selects the arguments and derived facts a person can see in an approval request. [Approval display](docs/runtime-and-concurrency.md#tool-approval-display) |
|
|
47
53
|
| `Phronomy::LLMAdapter::LMStudio` | Beta | Explicit known-output-error identification and bounded opt-in retry; [policy and evidence limits](docs/architecture/lm-studio-output-failures.md) |
|
|
48
54
|
| Agent `output_schema:`, `schema_delivery:`, `Agent::OutputError` and `OutputParser::JsonSchemaParser` | Beta | Agent returns validated immutable JSON or an explicit output failure; [contract and migration](docs/architecture/structured-output-and-scope.md) |
|
|
49
55
|
| Agent `transcript_messages` / `transcript_messages_async` | Beta | Read immutable conversation Message values with the same restoration rules as execution results; [reading conversation state](docs/getting-started.md) |
|
data/RELEASE_CHECKLIST.md
CHANGED
|
@@ -38,6 +38,10 @@ Copy it into the GitHub Release draft and check off each item.
|
|
|
38
38
|
- [ ] No new `requires_approval` tools missing the approval gate
|
|
39
39
|
- [ ] No secrets, credentials, or PII in tool descriptions, schema strings, or spec fixtures
|
|
40
40
|
- [ ] Dependency audit passes: `bundle exec bundler-audit check --update`
|
|
41
|
+
Run `bundle install` first; the development Gemfile provides the audit tool.
|
|
42
|
+
Audit the complete resolved bundle, including development and test dependencies,
|
|
43
|
+
without advisory exclusions. A runtime-only audit does not satisfy this gate.
|
|
44
|
+
The dependency-audit CI job and the release workflow enforce the same command.
|
|
41
45
|
|
|
42
46
|
---
|
|
43
47
|
|
|
@@ -83,10 +83,11 @@ with lazy grammar` is a known native schema/Tool rejection. It is classified as
|
|
|
83
83
|
stream operations. Unrelated string errors and requests without a schema retain
|
|
84
84
|
their ordinary transport-error behavior. The adapter neither downgrades schemas
|
|
85
85
|
nor replays requests. RubyLLM's configured transport retry policy is independent.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
On the supported HTTP stack, the known schema rejection retains its
|
|
87
|
+
`RubyLLM::BadRequestError` cause even when streaming. An HTTP 500 with the same
|
|
88
|
+
body retains its `RubyLLM::ServerError` cause. The
|
|
89
|
+
[HTTP dependency rationale](trust-acceptance-followup.md#http-dependency-correction)
|
|
90
|
+
documents the tested versions and request counts.
|
|
90
91
|
|
|
91
92
|
`Response` records finish_reason (stop, tool_calls, length, content_filter, refusal,
|
|
92
93
|
unknown), refusal and raw_finish_reason metadata. Only normal final completion can
|
|
@@ -37,6 +37,21 @@ retain the SDK server-error cause. Configured SDK retries for 429/500 remain;
|
|
|
37
37
|
a 400 rejection is one HTTP attempt even with three retries configured.
|
|
38
38
|
Partial-stream replay and callback identity regressions remain in place.
|
|
39
39
|
|
|
40
|
+
RubyLLM owns HTTP transport selection for model calls. Phronomy's LLM and
|
|
41
|
+
Embeddings adapters recognize Faraday exception classes at the SDK boundary.
|
|
42
|
+
The two gemspec declarations are a temporary workaround for the streaming
|
|
43
|
+
status bug. Remove both when all supported RubyLLM versions preserve the
|
|
44
|
+
status or exclude the affected HTTP stack through their own dependencies.
|
|
45
|
+
|
|
46
|
+
Verify removal with the two Phronomy declarations omitted. Resolve the minimum
|
|
47
|
+
supported RubyLLM and its permitted HTTP dependencies without the existing CI
|
|
48
|
+
pins. If the old pair still resolves, test it explicitly. If RubyLLM excludes
|
|
49
|
+
it, confirm resolution rejects it and test the new minimum. In both cases run
|
|
50
|
+
`spec/integration/transport_error_spec.rb` against the minimum and the normal
|
|
51
|
+
bundle. With three retries configured, a streaming HTTP 400 with a string
|
|
52
|
+
error body must retain its 400 cause and send one POST. An HTTP 500 with the
|
|
53
|
+
same body must retain its 500 cause and send four POSTs.
|
|
54
|
+
|
|
40
55
|
Before and after, application handling uses the same public API:
|
|
41
56
|
|
|
42
57
|
```ruby
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Context Policy budgeting
|
|
2
|
+
|
|
3
|
+
Applications choose how much model input capacity to use for one selection.
|
|
4
|
+
Context exposes that capacity and candidate estimates, while the framework keeps
|
|
5
|
+
its internal budget representation and final input validation.
|
|
6
|
+
|
|
7
|
+
For example, a review Policy can reserve part of the available input for its own
|
|
8
|
+
safety margin, then let DefaultPolicy choose history within the smaller limit.
|
|
9
|
+
No application needs to construct a private budget object for that operation.
|
|
10
|
+
|
|
11
|
+
## Reading and constraining input limits
|
|
12
|
+
|
|
13
|
+
A Policy input reports its current selection limit and can derive another input
|
|
14
|
+
with a smaller limit. Derivation preserves the original input and all its
|
|
15
|
+
material; it does not change model capabilities or generation settings.
|
|
16
|
+
|
|
17
|
+
For example, an input with a limit of 8192 can produce a selection input capped
|
|
18
|
+
at 5632. The original input still reports 8192. The derived input cannot later
|
|
19
|
+
increase its limit above 5632.
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
require "phronomy"
|
|
23
|
+
|
|
24
|
+
class ReservedInputPolicy < Phronomy::Context::ContextPolicy
|
|
25
|
+
def call(input)
|
|
26
|
+
limit = input.input_token_limit
|
|
27
|
+
unless limit
|
|
28
|
+
raise Phronomy::ConfigurationError, "Review requires known model input capacity"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
selection_limit = limit - limit / 4 - limit / 16
|
|
32
|
+
selected_input = input.with_input_token_limit(selection_limit)
|
|
33
|
+
Phronomy::Context::DefaultPolicy.instance.call(selected_input)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`input_token_limit` returns an Integer or `nil` for an unknown limit.
|
|
39
|
+
`with_input_token_limit(limit)` requires a positive Integer without coercion;
|
|
40
|
+
zero, negative values, strings and floating-point values raise `ArgumentError`.
|
|
41
|
+
Increasing a known limit also raises `ArgumentError`. The returned
|
|
42
|
+
`ContextPolicyInput` is immutable, and its other field values are unchanged.
|
|
43
|
+
|
|
44
|
+
When the original limit is unknown, an explicit positive selection cap is
|
|
45
|
+
allowed. That cap expresses application policy and does not establish that the
|
|
46
|
+
model can accept that input. Applications that require known capability must
|
|
47
|
+
reject `nil`, as the example above does.
|
|
48
|
+
|
|
49
|
+
The cap controls selection performed with the derived input. The framework's
|
|
50
|
+
final validation still uses the actual model budget; returning a different Plan
|
|
51
|
+
does not make the derived cap a new global validator. It does not set an output
|
|
52
|
+
token limit, alter a saved Manifest, or affect another Agent call.
|
|
53
|
+
|
|
54
|
+
## Estimating candidate content
|
|
55
|
+
|
|
56
|
+
A Policy can estimate proposed content through the same canonical encoding and
|
|
57
|
+
estimator used by its generated items. The Policy remains responsible for
|
|
58
|
+
choosing complete items and preserving required conversation groups.
|
|
59
|
+
|
|
60
|
+
For example, a retrieval Policy can fit a JSON page before adding it as
|
|
61
|
+
knowledge. JSON escaping contributes to the estimate because the complete
|
|
62
|
+
serialized page is measured.
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
require "phronomy"
|
|
66
|
+
require "json"
|
|
67
|
+
|
|
68
|
+
class PageKnowledgePolicy < Phronomy::Context::ContextPolicy
|
|
69
|
+
def initialize(page:, allowance:)
|
|
70
|
+
@page = page
|
|
71
|
+
@allowance = allowance
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def call(input)
|
|
75
|
+
content = JSON.generate(@page)
|
|
76
|
+
if estimate_tokens(content) > @allowance
|
|
77
|
+
raise Phronomy::Context::BudgetExceededError, "Retrieved page exceeds its allowance"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
page = knowledge_item(content: content, required: true)
|
|
81
|
+
Phronomy::Context::DefaultPolicy.instance.call(
|
|
82
|
+
input.with(knowledge: input.knowledge + [page])
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`estimate_tokens(content)` is a protected subclass helper. Strings are estimated
|
|
89
|
+
as text; other values use the framework's canonical JSON encoding. The helper
|
|
90
|
+
uses the existing estimator and does not introduce a second token-counting
|
|
91
|
+
algorithm. Estimates are approximate unless the configured estimator provides
|
|
92
|
+
model-specific counts. A successful estimate does not guarantee provider
|
|
93
|
+
acceptance or replace final input validation.
|
|
94
|
+
|
|
95
|
+
## Compatibility and ownership
|
|
96
|
+
|
|
97
|
+
These operations belong to the Context contract and reuse its existing budget
|
|
98
|
+
and estimation mechanisms. Applications own allocation ratios and retrieval
|
|
99
|
+
policy; DefaultPolicy retains its current selection algorithm.
|
|
100
|
+
|
|
101
|
+
For example, Example 22 can budget retrieved findings through these operations
|
|
102
|
+
while SharedState continues to coordinate Agents and application Tools continue
|
|
103
|
+
to own the finding store. No SharedState-to-Context dependency is introduced.
|
|
104
|
+
|
|
105
|
+
The operations are Experimental and available since v0.33.0. Existing private
|
|
106
|
+
budget and estimation classes remain implementation details. Existing ContextPolicy
|
|
107
|
+
factories and DefaultPolicy behavior are unchanged, and the protected helper
|
|
108
|
+
applies equally to normal Agent calls and SharedState member calls.
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "phronomy"
|
|
5
|
+
|
|
6
|
+
module SharedStateCustomSharingExample
|
|
7
|
+
class IssueBoard
|
|
8
|
+
attr_reader :closed
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@records = []
|
|
12
|
+
@closed = false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def record(content:, category:, status:, author:, cycle:)
|
|
16
|
+
raise "Issue board is closed" if closed
|
|
17
|
+
|
|
18
|
+
row = {id: @records.length + 1, content: content.dup.freeze,
|
|
19
|
+
category: category.dup.freeze, status: status.dup.freeze,
|
|
20
|
+
author: author.dup.freeze, cycle: cycle}.freeze
|
|
21
|
+
@records << row
|
|
22
|
+
row
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def search(category: nil, status: nil)
|
|
26
|
+
snapshot.select do |row|
|
|
27
|
+
(category.nil? || row.fetch(:category) == category) &&
|
|
28
|
+
(status.nil? || row.fetch(:status) == status)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def snapshot
|
|
33
|
+
raise "Issue board is closed" if closed
|
|
34
|
+
|
|
35
|
+
@records.dup.freeze
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def close
|
|
39
|
+
@records.clear
|
|
40
|
+
@closed = true
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class IssueSharing
|
|
45
|
+
def open(input:, config:)
|
|
46
|
+
board = IssueBoard.new
|
|
47
|
+
yield Phronomy::MultiAgent::SharedState::Binding.new(
|
|
48
|
+
store: board,
|
|
49
|
+
tools: ->(member:, cycle:) { tool_definitions(board, member, cycle) }
|
|
50
|
+
)
|
|
51
|
+
ensure
|
|
52
|
+
board&.close
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def tool_definitions(board, member, cycle)
|
|
58
|
+
{
|
|
59
|
+
record_tool(board, member.agent_definition.fetch(:id), cycle) => nil,
|
|
60
|
+
search_tool(board) => nil
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def record_tool(board, author, cycle)
|
|
65
|
+
Class.new(Phronomy::Tool::Base) do
|
|
66
|
+
tool_name "record_issue"
|
|
67
|
+
description "Record one issue with an explicit category and status."
|
|
68
|
+
execution_mode :cooperative
|
|
69
|
+
param :content, type: :string, desc: "The issue description."
|
|
70
|
+
param :category, type: :string, enum: %w[security performance],
|
|
71
|
+
desc: "The issue category."
|
|
72
|
+
param :status, type: :string, enum: %w[open closed],
|
|
73
|
+
desc: "The issue status."
|
|
74
|
+
|
|
75
|
+
define_method(:execute) do |content:, category:, status:|
|
|
76
|
+
board.record(content: content, category: category, status: status,
|
|
77
|
+
author: author, cycle: cycle).to_json
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def search_tool(board)
|
|
83
|
+
Class.new(Phronomy::Tool::Base) do
|
|
84
|
+
tool_name "search_issues"
|
|
85
|
+
description "Read issues matching all supplied category and status filters."
|
|
86
|
+
execution_mode :cooperative
|
|
87
|
+
param :category, type: :string, required: false, enum: %w[security performance],
|
|
88
|
+
desc: "Optional exact category filter."
|
|
89
|
+
param :status, type: :string, required: false, enum: %w[open closed],
|
|
90
|
+
desc: "Optional exact status filter."
|
|
91
|
+
|
|
92
|
+
define_method(:execute) do |category: nil, status: nil|
|
|
93
|
+
board.search(category: category, status: status).to_json
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
class Researcher < Phronomy::Agent::Base
|
|
100
|
+
agent_definition id: "shared-issue-researcher", version: 1
|
|
101
|
+
model ENV.fetch("PHRONOMY_EXAMPLE_MODEL", "configured-model")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
class ReviewTeam < Phronomy::MultiAgent::SharedState
|
|
105
|
+
sharing IssueSharing.new, id: "shared-issues", version: 1
|
|
106
|
+
member Researcher
|
|
107
|
+
max_cycles 2
|
|
108
|
+
coordination "Use search_issues to inspect relevant issues. " \
|
|
109
|
+
"Use record_issue to record each new issue with its category and status."
|
|
110
|
+
aggregate { |board| board.snapshot }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
module OfflineDemo
|
|
114
|
+
def self.run
|
|
115
|
+
opened_board = nil
|
|
116
|
+
output = IssueSharing.new.open(input: "Review authentication", config: {}) do |binding|
|
|
117
|
+
opened_board = binding.store
|
|
118
|
+
definitions = binding.tool_definitions(member: Researcher, cycle: 1)
|
|
119
|
+
tools = definitions.keys.to_h { |tool| [tool.tool_name, tool.new] }
|
|
120
|
+
recorder = tools.fetch("record_issue")
|
|
121
|
+
recorder.call({"content" => "Missing token validation", "category" => "security", "status" => "open"})
|
|
122
|
+
recorder.call({"content" => "Slow cache lookup", "category" => "performance", "status" => "open"})
|
|
123
|
+
recorder.call({"content" => "Session expiry corrected", "category" => "security", "status" => "closed"})
|
|
124
|
+
|
|
125
|
+
matches = JSON.parse(tools.fetch("search_issues").call({"category" => "security", "status" => "open"}))
|
|
126
|
+
unless matches.map { |row| row.fetch("content") } == ["Missing token validation"]
|
|
127
|
+
raise "Search did not apply both filters"
|
|
128
|
+
end
|
|
129
|
+
all_records = binding.store.snapshot
|
|
130
|
+
raise "Search changed the board" unless all_records.length == 3
|
|
131
|
+
|
|
132
|
+
{matches: matches, total_records: all_records.length}
|
|
133
|
+
end
|
|
134
|
+
raise "Provider did not close the board" unless opened_board.closed
|
|
135
|
+
|
|
136
|
+
puts JSON.pretty_generate(output.merge(provider_closed: true))
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
if $PROGRAM_NAME == __FILE__
|
|
142
|
+
abort "Run with --offline, or require this file from a configured Phronomy application." unless ARGV == ["--offline"]
|
|
143
|
+
|
|
144
|
+
SharedStateCustomSharingExample::OfflineDemo.run
|
|
145
|
+
end
|
data/docs/features.md
CHANGED
|
@@ -110,6 +110,8 @@ See the [migration guide](migrations/parallel-tool-chat-removal.md).
|
|
|
110
110
|
| **Completed native child release** — A host can unload the local Orchestrator and its finished direct child while preserving their saved results, conversation and coordination history. [Contract](runtime-and-concurrency.md#releasing-a-completed-orchestrator-and-child) | Beta |
|
|
111
111
|
| **`Phronomy::MultiAgent::TeamCoordinator`** — LLM coordinator with stateful worker Agents | Beta |
|
|
112
112
|
| **`Phronomy::MultiAgent::SharedState`** — Sequential peer-agent coordination through an invocation-local findings store; see the [namespace migration](migrations/shared-state-multi-agent.md) | Experimental |
|
|
113
|
+
| **Agent Tool composition** — `Agent::Base.with_tools(definitions, id:, version:)` derives a class with a complete replacement Tool map while preserving Phronomy authoring settings and filters; [contract](shared-state-sharing.md#deriving-an-agent-with-a-complete-tool-set) | Experimental (Unreleased) |
|
|
114
|
+
| **Custom shared data and Tools** — `SharedState.sharing(provider, id:, version:)` and `SharedState::Binding` connect application-owned stores and Tool definitions without private overrides; [contract and executable example](shared-state-sharing.md) | Experimental (Unreleased) |
|
|
113
115
|
| **Human-in-the-loop approval** — durable suspension publishes `:approval_required` through the Agent listener; `approve` / `approve_async` resumes the same logical `execution_id` with a fresh FSMSession incarnation | Beta |
|
|
114
116
|
| **`tool_approval_policy`** — Application-defined allow/approve/reject policy using a value-only `ApprovalEvaluationRequest` without live Agent/Tool references | Beta |
|
|
115
117
|
|
data/docs/mcp-client.md
CHANGED
|
@@ -10,8 +10,10 @@ Phronomy supports the official `mcp` Ruby SDK **1.x**. MCP SDK 0.x is not suppor
|
|
|
10
10
|
- `http://...`
|
|
11
11
|
- `https://...`
|
|
12
12
|
|
|
13
|
-
HTTP and
|
|
14
|
-
`event_stream_parser
|
|
13
|
+
For HTTP and HTTPS servers, Phronomy delegates to the MCP SDK's
|
|
14
|
+
`MCP::Client::HTTP` transport. RubyLLM already brings in `event_stream_parser`
|
|
15
|
+
for server-sent events. See the [HTTP dependency rationale](architecture/trust-acceptance-followup.md#http-dependency-correction)
|
|
16
|
+
for Phronomy's Faraday and Net::HTTP adapter requirements.
|
|
15
17
|
|
|
16
18
|
## Input-schema subset
|
|
17
19
|
|
|
@@ -42,3 +42,8 @@ for one invocation only.
|
|
|
42
42
|
|
|
43
43
|
See [ADR-053](../decisions/053-shared-state-coordination-ownership.md) for the
|
|
44
44
|
ownership and compatibility decision.
|
|
45
|
+
|
|
46
|
+
The later [custom sharing extension](../shared-state-sharing.md) is opt-in and
|
|
47
|
+
leaves the default behavior described above intact. It gives an application
|
|
48
|
+
explicit ownership of its store and Tool definitions; its generated identity
|
|
49
|
+
also records the application's sharing ID and semantic version.
|
|
@@ -211,6 +211,37 @@ Worker authorization/execution outcomes return as values carrying
|
|
|
211
211
|
`tool_invocation_id`; the Tool FSMSession consumes a mismatched semantic result
|
|
212
212
|
without advancing its current state.
|
|
213
213
|
|
|
214
|
+
## Tool approval display
|
|
215
|
+
|
|
216
|
+
A Tool chooses which arguments and derived facts a person may see. Agent asks
|
|
217
|
+
the Tool for both when it builds a new `ToolApprovalRequest` item and takes a
|
|
218
|
+
snapshot of the result. Approval policy and Tool execution use their own inputs.
|
|
219
|
+
|
|
220
|
+
For example, a Tool can declare `redact_params :token`. The default
|
|
221
|
+
`Tool::Base#approval_display(arguments:, facts:)` masks `token` in the
|
|
222
|
+
argument display and masks string values in facts. A numeric fact such as
|
|
223
|
+
`recipient_count: 3` can still be shown. Use fixed fact names and choose
|
|
224
|
+
aggregates that are appropriate to disclose.
|
|
225
|
+
|
|
226
|
+
If `redact_params :auth` covers `auth: {pin: 627018}`, the default also
|
|
227
|
+
masks a fact such as `echo: 627018`. It checks JSON values copied from inside
|
|
228
|
+
sensitive hashes and arrays. A matching fact key causes that entry to be
|
|
229
|
+
omitted. The declared
|
|
230
|
+
argument's own hash keys are treated as field names. If those names are
|
|
231
|
+
themselves confidential, keep them out of fact keys. Equality also masks
|
|
232
|
+
unrelated facts with the same value. The Tool author chooses which computed
|
|
233
|
+
values may be shown. For example, an arithmetic transformation of a PIN can
|
|
234
|
+
still disclose it even if the result differs from the original. Override
|
|
235
|
+
`approval_display` to explicitly select safe fields when publishing such facts.
|
|
236
|
+
|
|
237
|
+
To customize both fields, return a Hash with the symbol keys `:arguments` and
|
|
238
|
+
`:facts`, each holding a displayable Hash. The hook runs on EventLoop while
|
|
239
|
+
Agent prepares the approval. Build the result from the supplied values without
|
|
240
|
+
blocking or performing I/O. A wrapper can forward the operation to its
|
|
241
|
+
underlying Tool. If a Tool omits the operation, the item contains an
|
|
242
|
+
empty Hash for each field. Existing saved approval items retain the display
|
|
243
|
+
data recorded when they were created.
|
|
244
|
+
|
|
214
245
|
## Approval suspension and live owner lookup
|
|
215
246
|
|
|
216
247
|
Approval suspension retains the same process-local Agent and AgentInvocation but
|
|
@@ -672,6 +703,11 @@ Runtime shutdown before resetting configuration.
|
|
|
672
703
|
|
|
673
704
|
A host can release one suspended Agent and continue using its Ruby process for other Agents. Hold one external claim per mutable Agent while loading, executing, detaching and recording delivery. Persist a new request's reserved execution ID before dispatch. A host uses the existing incarnation-level listener to receive the approval request.
|
|
674
705
|
|
|
706
|
+
The hosted handle matches the requested approval to its saved wait and local
|
|
707
|
+
owner. That owner's execution coordinator submits the detach command to
|
|
708
|
+
EventLoop, which rechecks ownership before releasing the Agent. The host waits
|
|
709
|
+
for the result before handing the execution to another worker.
|
|
710
|
+
|
|
675
711
|
For example, worker A starts a fresh reservation with `agent.start_hosted(reservation:, input:)`. If the run finishes directly, `handle.wait_result` returns the ordinary execution result. On an approval notification, call `handle.detach!(approval_request_id: request.id)` outside EventLoop. Success returns an immutable `ApprovalWait` with the execution ID, approval request ID and committed revision. The same handle then returns that wait. The released Agent object cannot be reused. Existing `invoke_async` handles keep their terminal-result contract.
|
|
676
712
|
|
|
677
713
|
An incorrect or stale approval, outstanding physical work, concurrent operation, live scope or durable retention prevents transfer. A busy response leaves the host responsible for the claim. A spent reservation is rejected. After a verified worker exit, a host can query `store.standalone_approval_wait(agent_id:, execution_id:)` without loading the Agent. A missing or ineligible wait returns nil. Store errors and owner mismatches propagate. Only reclaim a delivery if the original worker is known to have stopped and its claim still matches.
|