phronomy 0.31.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 +141 -0
- data/README.md +13 -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 +4 -0
- data/docs/mcp-client.md +4 -2
- data/docs/migrations/shared-state-multi-agent.md +5 -0
- data/docs/runtime-and-concurrency.md +195 -0
- data/docs/shared-state-sharing.md +223 -0
- data/lib/phronomy/agent/approval_wait.rb +8 -0
- data/lib/phronomy/agent/async_event_api.rb +60 -0
- data/lib/phronomy/agent/base.rb +54 -1
- data/lib/phronomy/agent/execution/exact_execution.rb +4 -0
- data/lib/phronomy/agent/execution/execution_coordinator.rb +100 -2
- data/lib/phronomy/agent/journal/journal_projection.rb +24 -1
- data/lib/phronomy/agent/lifecycle/hosted_invocation.rb +67 -0
- data/lib/phronomy/agent/lifecycle/lifetime_api.rb +30 -0
- data/lib/phronomy/agent/lifecycle/operation_lease.rb +7 -0
- data/lib/phronomy/agent/lifecycle/operation_result.rb +12 -0
- data/lib/phronomy/agent/lifecycle/ownership_registry.rb +86 -14
- data/lib/phronomy/agent/recovery/recovery_coordinator/installation.rb +1 -1
- data/lib/phronomy/agent/store.rb +52 -0
- 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/context/context_policy_input_builder.rb +27 -19
- data/lib/phronomy/engine/fsm_session.rb +9 -3
- data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +87 -5
- data/lib/phronomy/multi_agent/orchestrator.rb +30 -18
- 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/lib/phronomy/workflow/execution/workflow.rb +9 -0
- data/lib/phronomy/workflow/execution/workflow_runner.rb +62 -14
- data/scripts/api_snapshot.rb +8 -3
- data/sig/phronomy/agent/execution_contract.rbs +13 -0
- data/sig/phronomy/agent/store.rbs +1 -0
- data/sig/phronomy/agent.rbs +2 -0
- data/sig/phronomy/context.rbs +3 -0
- data/sig/phronomy/multi_agent.rbs +28 -2
- data/sig/phronomy/tool.rbs +1 -0
- data/sig/phronomy/workflow.rbs +1 -0
- metadata +9 -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,147 @@ 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
|
+
|
|
92
|
+
## [0.32.0] - 2026-11-03
|
|
93
|
+
|
|
94
|
+
### Added
|
|
95
|
+
|
|
96
|
+
- Add `Agent#start_hosted`, `Agent::HostedInvocation` and immutable
|
|
97
|
+
`Agent::ApprovalWait` for safely detaching an exclusively owned standalone Agent
|
|
98
|
+
at its first committed Tool approval wait. A new incarnation can use the
|
|
99
|
+
existing `load` and `approve` operations to continue the same execution.
|
|
100
|
+
- Add `Agent::Store#standalone_approval_wait` to query the committed approval fact without
|
|
101
|
+
transferring a live worker claim. Delivery deduplication, exclusive claims and
|
|
102
|
+
verified worker termination remain application responsibilities. Standalone
|
|
103
|
+
detachment does not transfer parent Workflows or shared children; supported
|
|
104
|
+
direct-child handoff and saved Workflow events are separate APIs below.
|
|
105
|
+
|
|
106
|
+
- Add `Workflow#send_saved_event` to restore a declared wait by Workflow instance
|
|
107
|
+
ID and expected revision before applying an event. The host can reject a
|
|
108
|
+
delayed delivery even when the same Workflow later returns to an identical wait.
|
|
109
|
+
- Allow an Orchestrator's hosted handle to detach its local parent and direct
|
|
110
|
+
child together at a saved child Tool approval. The host supplies the child's
|
|
111
|
+
execution and approval request IDs and retains the parent's execution ID for
|
|
112
|
+
resumption on another worker. Both workers can remain alive.
|
|
113
|
+
- Add `MultiAgent::Orchestrator#approve_hosted_child` to answer a restored
|
|
114
|
+
direct child's approval with a hosted handle. If the same execution asks for
|
|
115
|
+
another approval, the worker can release the pair at the new wait. A third
|
|
116
|
+
worker can restore that wait without replaying the earlier Tool call.
|
|
117
|
+
- Add `MultiAgent::Orchestrator#unload_with_completed_child!` for a worker
|
|
118
|
+
that has finished a direct child dispatch. It can release the local owners
|
|
119
|
+
while keeping the conversation, execution results and native slot history.
|
|
120
|
+
|
|
121
|
+
### Fixed
|
|
122
|
+
|
|
123
|
+
- Bind native subagent dispatch context to each prepared Tool instance so that
|
|
124
|
+
cached Tool methods do not keep completed Orchestrators alive after unload
|
|
125
|
+
(Issue #440). Saved SQLite execution and conversation history is preserved.
|
|
126
|
+
- Keep distinct native Tool call/result pairs when a provider reuses its call ID
|
|
127
|
+
in later responses on the same Agent. A subsequent request can use the saved
|
|
128
|
+
conversation without executing either Tool again to rebuild context.
|
|
129
|
+
- Restore mutable Workflow context fields from durable checkpoints when
|
|
130
|
+
delivering a saved event or starting another invocation of the same instance.
|
|
131
|
+
Actions can update nested strings, arrays and hashes without changing the
|
|
132
|
+
original checkpoint.
|
|
133
|
+
- Continue using an Agent after a human rejects a Tool call. Model input for the
|
|
134
|
+
next turn omits the rejected, incomplete Tool exchange. The original journal
|
|
135
|
+
and public conversation history remain available for inspection.
|
|
136
|
+
|
|
137
|
+
### Compatibility and verification
|
|
138
|
+
|
|
139
|
+
- Worker APIs require 0.32.0 or later; applications using `start_hosted` cannot
|
|
140
|
+
run against 0.31.0. The existing [0.31 storage and Agent Tool migration
|
|
141
|
+
requirements](docs/migrations/0.31.md) still apply. This release does not add a
|
|
142
|
+
persistence mapping migration or change those stored execution formats.
|
|
143
|
+
- Native handoff and completed-owner release cover one direct child in an
|
|
144
|
+
isolated dispatch batch. Delivery deduplication, exclusive claims, ACKs and
|
|
145
|
+
verified worker termination belong to the application. General graph transfer,
|
|
146
|
+
multi-host fencing, supervisor restart and recovery of unknown external Tool
|
|
147
|
+
effects are not established by these examples.
|
|
148
|
+
- Worker acceptance uses fixed HTTP responses, actual Ruby processes and SQLite,
|
|
149
|
+
including Ubuntu 24.04 and 26.04. It does not establish live-model acceptance.
|
|
150
|
+
- LM Studio/gpt-oss PEG parsing remains a known unresolved limitation. The latest
|
|
151
|
+
pre-release standard verifier reported 101 passed, 2 failed and 0 skipped;
|
|
152
|
+
Examples 01 and 22 received provider PEG errors, also reproduced before Worker
|
|
153
|
+
support. Known-error identification and bounded retry do not fix the provider's
|
|
154
|
+
root cause. These two failures are tracked separately from Worker acceptance.
|
|
155
|
+
|
|
15
156
|
## [0.31.0] - 2026-10-25
|
|
16
157
|
|
|
17
158
|
### Added
|
data/README.md
CHANGED
|
@@ -37,18 +37,30 @@ 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.
|
|
43
|
+
Worker hosting and completed parent/child release require v0.32.0 or later.
|
|
41
44
|
Review the [0.31 migration notes](docs/migrations/0.31.md) before upgrading
|
|
42
45
|
persistent applications or custom Agent Tool subclasses.
|
|
43
46
|
|
|
44
47
|
| API | Stability | Contract |
|
|
45
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) |
|
|
46
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) |
|
|
47
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) |
|
|
48
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) |
|
|
49
56
|
| `Phronomy::GeneratorVerifier`, its approval/lifetime operations and immutable `Result` with output-failure diagnostics | Beta | [Generation and structured-output contract](docs/architecture/structured-output-and-scope.md) |
|
|
50
57
|
| `Agent.open_scope`, `Agent::Scope`, `Agent::Base#unload_async / #unload!`, `AgentUnloadedError` and `Agent::ScopeCleanupError` | Beta | Runtime-bound ownership and safe release of idle Agent incarnations without changing saved history or closing the Store; [contract](docs/architecture/dx05-agent-scope.md) |
|
|
58
|
+
| `Agent::Base#start_hosted`, `Agent::HostedInvocation`, `Agent::ApprovalWait` and `Agent::Store#standalone_approval_wait` | Beta | Added in v0.32.0. Detach an exclusively owned standalone Agent at its first committed approval wait, then restore the same execution in another process; delivery claims and worker fencing remain application responsibilities; [contract](docs/runtime-and-concurrency.md#hosting-a-standalone-agent-through-approval) |
|
|
59
|
+
| `Agent::HostedInvocation#detach!(execution_id:, approval_request_id:)` | Beta | Added in v0.32.0. Release an exclusively owned Orchestrator and its single direct subagent at the child's approval wait. A second worker can resume both saved executions while the first worker handles other requests. [Contract](docs/runtime-and-concurrency.md#hosting-an-orchestrator-through-a-child-approval) |
|
|
60
|
+
| `MultiAgent::Orchestrator#approve_hosted_child` | Beta | Added in v0.32.0. Answer the current approval on a restored direct child. The returned handle can release the pair again if that child requests another approval during the same execution. [Contract](docs/runtime-and-concurrency.md#hosting-an-orchestrator-through-a-child-approval) |
|
|
61
|
+
| `MultiAgent::Orchestrator#unload_with_completed_child!` | Beta | Added in v0.32.0. Release local owners after a direct child result has been imported and the parent has finished. The saved executions, transcripts and coordination history remain available. [Contract](docs/runtime-and-concurrency.md#releasing-a-completed-orchestrator-and-child) |
|
|
51
62
|
| `Tools::Agent::CleanupError` | Beta | Added in v0.31.0. Preserves the child, result and failure causes when safe release fails; retry `child_agent.unload_async` for release only; [contract](docs/architecture/agent-tool-child-lifetime.md) |
|
|
63
|
+
| `Workflow#send_saved_event` | Beta | Added in v0.32.0. Deliver an event to a specific revision of a persisted Workflow wait; [contract](docs/runtime-and-concurrency.md#delivering-an-event-to-a-saved-workflow-wait) |
|
|
52
64
|
| `Workflow::Builder#task_state` and `WorkflowTaskResumeError` | Beta | State-entry-scoped Task completion, live cancellation and explicit restart rejection; [contract](docs/architecture/dx05-workflow-task-state.md) |
|
|
53
65
|
| `Phronomy::Integrations::OrderedEventDelivery` | Beta | Optional ordered delivery with bounded admission and explicit drain lifetime; [usage](docs/ordered-event-delivery.md) |
|
|
54
66
|
| `Phronomy::Error#code` and error constructor `code:` | Beta | Optional reason codes and bounded causes retained in saved failure diagnostics; [meaning and compatibility](docs/architecture/error-api-review.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
|
@@ -106,8 +106,12 @@ See the [migration guide](migrations/parallel-tool-chat-removal.md).
|
|
|
106
106
|
| **Durable Handoff** — `MultiAgent::Handoff` and `MultiAgent::HandoffRunner` persist responsibility, immutable Context and exact Target reservation in one Persistence domain; current graph wiring is required for continuation | Beta |
|
|
107
107
|
| **GeneratorVerifier** — Generator-Verifier loop with application prompts, Agent-validated output schemas and explicit output failures | Beta |
|
|
108
108
|
| **`Phronomy::MultiAgent::Orchestrator`** — Parallel subagent dispatch, fan-out, and `subagent` DSL | Beta |
|
|
109
|
+
| **Hosted child approvals** — An Orchestrator can release a single direct child and its parent at successive Tool approvals within the same child execution. The host transfers exclusive ownership between workers. [Contract](runtime-and-concurrency.md#hosting-an-orchestrator-through-a-child-approval) | Beta |
|
|
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 |
|
|
109
111
|
| **`Phronomy::MultiAgent::TeamCoordinator`** — LLM coordinator with stateful worker Agents | Beta |
|
|
110
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) |
|
|
111
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 |
|
|
112
116
|
| **`tool_approval_policy`** — Application-defined allow/approve/reject policy using a value-only `ApprovalEvaluationRequest` without live Agent/Tool references | Beta |
|
|
113
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.
|