phronomy 0.30.1 → 0.31.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 +65 -0
- data/README.md +7 -2
- data/docs/architecture/agent-tool-child-lifetime.md +141 -0
- data/docs/architecture/dx05-agent-scope.md +4 -2
- data/docs/architecture/structured-output-and-scope.md +73 -13
- data/docs/decisions/044-agent-default-and-one-shot-composition.md +5 -0
- data/docs/getting-started.md +26 -0
- data/docs/migrations/0.31.md +72 -0
- data/lib/phronomy/agent/async_event_api.rb +17 -8
- data/lib/phronomy/agent/base.rb +27 -2
- data/lib/phronomy/agent/composition/run_once.rb +11 -10
- data/lib/phronomy/agent/context_preparation.rb +1 -0
- data/lib/phronomy/agent/execution/dispatch_preparation.rb +1 -1
- data/lib/phronomy/agent/execution/exact_execution.rb +5 -3
- data/lib/phronomy/agent/execution/execution_coordinator.rb +6 -0
- data/lib/phronomy/agent/execution/execution_outcome_committer.rb +1 -6
- data/lib/phronomy/agent/execution/initial_preparation.rb +1 -1
- data/lib/phronomy/agent/execution/invocation_actions.rb +17 -15
- data/lib/phronomy/agent/execution_change.rb +4 -0
- data/lib/phronomy/agent/output_contract.rb +47 -5
- data/lib/phronomy/agent/persistence/codec.rb +16 -2
- data/lib/phronomy/agent/recovery/recovery_coordinator/continuation.rb +1 -0
- data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +2 -2
- data/lib/phronomy/agent/runtime_input.rb +7 -0
- data/lib/phronomy/engine/fsm_session.rb +7 -0
- data/lib/phronomy/execution/async_operation.rb +16 -0
- data/lib/phronomy/execution/concurrency/result_finalization.rb +106 -0
- data/lib/phronomy/generation/generator_verifier/agent_operation.rb +3 -3
- data/lib/phronomy/generation/generator_verifier.rb +6 -3
- data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +1 -1
- data/lib/phronomy/llm_adapter/backends/structured_response_capture.rb +3 -1
- data/lib/phronomy/tools/agent.rb +58 -22
- data/lib/phronomy/version.rb +1 -1
- data/sig/phronomy/agent.rbs +7 -5
- data/sig/phronomy/execution/async_operation.rbs +1 -0
- data/sig/phronomy/generation/generator_verifier.rbs +1 -1
- data/sig/phronomy/tools/agent.rbs +14 -0
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e7eb1f72acaa35f02d5f848dac344ea971e6ef190544d2b200fe367409e48e92
|
|
4
|
+
data.tar.gz: 26b2de048b30845bdbf973a5e26a6d5fa4b734295d31b81cec2530e9dd896dc8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eaf92481864d37b545ccf7507541fbd64777eb6ff6369e2f8a674f31341bc2ec439883dfa2964d410c1becc2adc92c2794c9a7a2e6a7a2e5050a78e2ae4fa181
|
|
7
|
+
data.tar.gz: 6ca6f91cc3bf270a2694cb5a8a194cc1f4fe8270440ada751a4082f0ae27489c0798743a351d8850e1fbf4e81eb0aac207bee563995e8aa821d2008b2b6916e2
|
data/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,69 @@ Release history for 0.14.0 and earlier is archived in
|
|
|
12
12
|
|
|
13
13
|
## [Unreleased]
|
|
14
14
|
|
|
15
|
+
## [0.31.0] - 2026-10-25
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Add explicit Agent `schema_delivery: :native | :none` across execution entry
|
|
20
|
+
points and GeneratorVerifier. Local structured-output validation is identical
|
|
21
|
+
in both modes; existing calls retain native delivery.
|
|
22
|
+
- Add `Agent#transcript_messages` and its async counterpart to read immutable
|
|
23
|
+
Message values without application knowledge of Journal storage.
|
|
24
|
+
- Add `Tools::Agent::CleanupError` (`tool.agent_cleanup_failed`) with the child
|
|
25
|
+
Agent, successful result and primary/cleanup failures needed to diagnose failed
|
|
26
|
+
release and retry `child_agent.unload_async` without repeating the invocation.
|
|
27
|
+
|
|
28
|
+
### Persistence
|
|
29
|
+
|
|
30
|
+
- Save new output contracts as version 2 with the delivery choice and retain their
|
|
31
|
+
schemas even when no schema is sent. These executions use envelope 0.3, rejected
|
|
32
|
+
by older readers. Existing version-1 contracts remain native and readable.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- Preserve streamed refusal facts even when native schema delivery is omitted.
|
|
37
|
+
- Release the one-shot Agent owned by `Agent.run_once` through `Agent.open_scope`.
|
|
38
|
+
Preserve the invocation value or original exception when release succeeds;
|
|
39
|
+
report unsafe or failed release through `Agent::ScopeCleanupError` with the
|
|
40
|
+
result, primary error and scope needed for a release-only retry.
|
|
41
|
+
- Give each Agent-backed Tool call explicit ownership of its fresh child and
|
|
42
|
+
ephemeral Store. Complete release after logical and physical invocation
|
|
43
|
+
completion, without occupying a worker merely to wait. Preserve approval,
|
|
44
|
+
cancellation, independent leases, retention and recovery restrictions.
|
|
45
|
+
- Acknowledge terminal execution waiters before publishing their result while
|
|
46
|
+
retaining each operation's physical lease until its work actually finishes.
|
|
47
|
+
- Stop completed Agent Tool handles from retaining released children through
|
|
48
|
+
generated callback owners, temporary state-machine tracker context or completed
|
|
49
|
+
`ExecutionChange` wiring. Clear execution-only references after commit and any
|
|
50
|
+
ambiguous-outcome readback, including failure paths. Keep single-use guards,
|
|
51
|
+
saved results, session ownership and recovery behavior intact.
|
|
52
|
+
|
|
53
|
+
### Changed
|
|
54
|
+
|
|
55
|
+
- Both synchronous and asynchronous Agent Tool calls now use the child's
|
|
56
|
+
`invoke_async`. An override of only `invoke` no longer changes synchronous
|
|
57
|
+
Agent Tool execution; update custom child subclasses accordingly.
|
|
58
|
+
- Agent Tool cleanup failures are surfaced and diagnosed, including after an
|
|
59
|
+
outer observer cancels. A general Tool error-suppression policy cannot hide
|
|
60
|
+
failed child release.
|
|
61
|
+
|
|
62
|
+
### Verification and migration
|
|
63
|
+
|
|
64
|
+
- Read the [0.31 migration notes](docs/migrations/0.31.md) before upgrading stored
|
|
65
|
+
executions or custom Agent Tool subclasses. Deploy readers that support
|
|
66
|
+
AgentExecution 0.3 before writing new structured executions; older readers
|
|
67
|
+
cannot read these records, including completed executions. Existing 0.2 records
|
|
68
|
+
and version-1 native output contracts remain readable without a read-time rewrite.
|
|
69
|
+
- LM Studio/gpt-oss PEG output parsing remains an unresolved external limitation.
|
|
70
|
+
Existing known-error identification and bounded retry are not a root-cause fix.
|
|
71
|
+
The DX1/DX2 live normal path, streaming, Tool use and history restoration remain
|
|
72
|
+
unverified in that environment. This release relies on source review, offline
|
|
73
|
+
regressions and CI; it does not claim completion of live LLM acceptance.
|
|
74
|
+
Historical initial success and bounded retry/stop evidence retain their limited
|
|
75
|
+
scope; retry recovery and recurrence resolution remain unverified.
|
|
76
|
+
|
|
77
|
+
|
|
15
78
|
## [0.30.1] - 2026-10-24
|
|
16
79
|
|
|
17
80
|
### Documentation
|
|
@@ -40,6 +103,8 @@ Runtime behavior, public APIs and dependency requirements match 0.30.0.
|
|
|
40
103
|
|
|
41
104
|
### Changed
|
|
42
105
|
|
|
106
|
+
- Generate architecture SVG metadata in English, including repeated formatting.
|
|
107
|
+
|
|
43
108
|
- GeneratorVerifier owns immutable evaluation Results, explicit approval routing
|
|
44
109
|
and Agent lifetime. Use `accepted?`, `termination_reason` and
|
|
45
110
|
`raise_if_unaccepted`; `output_failed` distinguishes invalid output from a
|
data/README.md
CHANGED
|
@@ -37,14 +37,18 @@ 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 application APIs
|
|
40
|
+
The following table describes the application APIs available in v0.31.0.
|
|
41
|
+
Review the [0.31 migration notes](docs/migrations/0.31.md) before upgrading
|
|
42
|
+
persistent applications or custom Agent Tool subclasses.
|
|
41
43
|
|
|
42
44
|
| API | Stability | Contract |
|
|
43
45
|
|---|---|---|
|
|
44
46
|
| `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) |
|
|
45
|
-
| Agent `output_schema:`, `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) |
|
|
47
|
+
| 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
|
+
| 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) |
|
|
46
49
|
| `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) |
|
|
47
50
|
| `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) |
|
|
51
|
+
| `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) |
|
|
48
52
|
| `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) |
|
|
49
53
|
| `Phronomy::Integrations::OrderedEventDelivery` | Beta | Optional ordered delivery with bounded admission and explicit drain lifetime; [usage](docs/ordered-event-delivery.md) |
|
|
50
54
|
| `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) |
|
|
@@ -189,6 +193,7 @@ contracts, timeout/cancellation semantics, metrics, and callback rules.
|
|
|
189
193
|
- [Persistence failure contract migration](docs/migrations/persistence-failure-contracts.md) — domain rescue clauses and unchanged raw backend errors.
|
|
190
194
|
- [0.19 unified Persistence migration](docs/migrations/0.19.md).
|
|
191
195
|
- [0.22 semantic Multi-Agent Handoff migration](docs/migrations/0.22.md).
|
|
196
|
+
- [0.31 persistence and Agent lifetime migration](docs/migrations/0.31.md).
|
|
192
197
|
- [Architecture Decision Records](docs/decisions/) — design rationale and superseding decisions.
|
|
193
198
|
- [CHANGELOG](CHANGELOG.md) — current development and recent release history.
|
|
194
199
|
- [Changelog archive: 0.14.0 and earlier](docs/changelog/0.14-and-earlier.md).
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Agent Tool child lifetime
|
|
2
|
+
|
|
3
|
+
`Tools::Agent.from_agent` owns one fresh Agent and ephemeral Store per call.
|
|
4
|
+
The Tool releases its private live child after the invocation and its physical
|
|
5
|
+
work finish. Ordinary application-owned Agents and durable MultiAgent children
|
|
6
|
+
retain their own explicit lifetime and recovery policies.
|
|
7
|
+
|
|
8
|
+
## Public behavior
|
|
9
|
+
|
|
10
|
+
`from_agent`, `call`, `call_async` and `execute` keep their arguments. Successful
|
|
11
|
+
output remains `result[:output].to_s`, including the empty String for nil. Tool
|
|
12
|
+
validation, cancellation-token precedence, error policy, truncation and configured
|
|
13
|
+
result-transform order remain in their existing owners.
|
|
14
|
+
|
|
15
|
+
The synchronous `execute` now waits for the same internal asynchronous operation
|
|
16
|
+
used by `call_async`. Both invoke the child's `invoke_async` implementation.
|
|
17
|
+
An application override of only `invoke` no longer changes the synchronous Tool
|
|
18
|
+
path; there is no legacy dispatch branch. Synchronous EventLoop entry is rejected
|
|
19
|
+
before creating a Store or child.
|
|
20
|
+
|
|
21
|
+
Normal Tool completion includes release. No OffloadPool worker or new Thread is
|
|
22
|
+
used merely to wait for the child or its release. The existing Agent release can
|
|
23
|
+
perform its ordinary asynchronous persistence checks. Records are not purged,
|
|
24
|
+
and unrelated Agents or shared stores are not shut down.
|
|
25
|
+
|
|
26
|
+
## Cancellation and approval
|
|
27
|
+
|
|
28
|
+
Logical result settlement does not prove that physical work has ended. The
|
|
29
|
+
operation keeps ownership until both source boundaries are complete, then calls
|
|
30
|
+
the existing `unload_async`. Cancelling an observation handle does not cancel
|
|
31
|
+
this finalization or silently release an active child. Use the accepted
|
|
32
|
+
CancellationToken to request operation-wide cancellation.
|
|
33
|
+
|
|
34
|
+
Pending approval is not terminal. This Tool neither auto-approves nor adds a
|
|
35
|
+
child-event forwarding API. A child requiring external approval still needs an
|
|
36
|
+
application-defined way to deliver that approval. Active work, other accepted
|
|
37
|
+
operations, retention and unresolved recovery continue to prevent release.
|
|
38
|
+
|
|
39
|
+
When several Agent calls observe the same terminal execution, C04 acknowledges
|
|
40
|
+
all of those finished operations before publishing any logical result. Otherwise
|
|
41
|
+
the first result callback could request release before a sibling approval waiter
|
|
42
|
+
has acknowledged completion. Each OperationResult still waits for its own tracked
|
|
43
|
+
physical work. Independent operations and synchronous caller leases remain live;
|
|
44
|
+
they are not force-released by this batching.
|
|
45
|
+
|
|
46
|
+
## Cleanup failure
|
|
47
|
+
|
|
48
|
+
On successful cleanup, the operation preserves its output or original exception.
|
|
49
|
+
Ordinary Tool error policy is applied afterward. A cleanup failure instead raises
|
|
50
|
+
`Tools::Agent::CleanupError`, a `ToolError` with code
|
|
51
|
+
`tool.agent_cleanup_failed`, even when the Tool suppresses ordinary failures.
|
|
52
|
+
|
|
53
|
+
| Reader | Meaning |
|
|
54
|
+
| --- | --- |
|
|
55
|
+
| `child_agent` | The exact child instance whose release failed |
|
|
56
|
+
| `agent_id` | That child's identity |
|
|
57
|
+
| `result` | The successful String before Tool truncation, or nil |
|
|
58
|
+
| `primary_error` | Invocation or conversion failure, when present |
|
|
59
|
+
| `cleanup_error` | Release failure |
|
|
60
|
+
| `cause` | The primary error when present, otherwise the cleanup error |
|
|
61
|
+
|
|
62
|
+
The application may retry `error.child_agent.unload_async` when the release
|
|
63
|
+
condition has been resolved. This retries release only: it never invokes the
|
|
64
|
+
Agent, model or Tool again. The error and live child reference are process-local;
|
|
65
|
+
persisted error diagnostics do not reconstruct an owner or retry capability.
|
|
66
|
+
|
|
67
|
+
Every cleanup failure is also reported with its child identity, stable code and
|
|
68
|
+
both available failure diagnostics. This includes failures after an outer
|
|
69
|
+
observer has already cancelled its handle. The settled observer is not overwritten.
|
|
70
|
+
The owner remains registered for inspection through the existing Agent API.
|
|
71
|
+
Diagnostic failures do not replace the operation error or repeat work.
|
|
72
|
+
|
|
73
|
+
## Responsibility boundaries
|
|
74
|
+
|
|
75
|
+
| Owner | Responsibility |
|
|
76
|
+
| --- | --- |
|
|
77
|
+
| I04, concrete Agent Tool | Fresh child acquisition, invocation, output conversion, release request and child-specific failure information |
|
|
78
|
+
| C04, Agent | Admission, physical-operation leases, approval, recovery, retention, unload safety and terminal waiter acknowledgment |
|
|
79
|
+
| C06, Tool | Input validation, general error policy, truncation and result transformations |
|
|
80
|
+
| C13, Execution | Neutral logical/physical completion and asynchronous finalization composition |
|
|
81
|
+
|
|
82
|
+
The private `AsyncOperation.finalize` captures startup failure, observes a
|
|
83
|
+
TaskResult's logical and physical completion, starts one supplied cleanup
|
|
84
|
+
operation and observes its completion. The owner supplies error combination.
|
|
85
|
+
It is independent of application Execution cancellation and contains no Agent,
|
|
86
|
+
Store, Tool or provider policy. A callback-only source is not accepted by this
|
|
87
|
+
private finalization API; the start and cleanup functions must return TaskResults.
|
|
88
|
+
|
|
89
|
+
This does not add a public finally, async scope or one-shot Agent API. The
|
|
90
|
+
concrete Tool does not regain a dependency on the application `run_once`
|
|
91
|
+
composition, and does not manually settle TaskResults. Agent release decisions
|
|
92
|
+
are not moved into Execution or Engine.
|
|
93
|
+
|
|
94
|
+
## Runtime reference lifetime
|
|
95
|
+
|
|
96
|
+
Agent entry actions resolve their owner from the invocation being processed.
|
|
97
|
+
They do not curry a live Agent into the generated state-machine class. This
|
|
98
|
+
prevents VM method caches for generated callbacks from extending Agent lifetime.
|
|
99
|
+
|
|
100
|
+
FSMSession owns its context between events. The state-machine tracker receives
|
|
101
|
+
that context while an event is dispatched, then drops its temporary reference
|
|
102
|
+
when dispatch returns, including on errors. Later events rebind the same live
|
|
103
|
+
session context. This does not clear the session's context, end pending work,
|
|
104
|
+
change approval handling, or relax Agent unload admission.
|
|
105
|
+
|
|
106
|
+
For example, retaining a completed Tool handle must not retain the child through
|
|
107
|
+
either generated callbacks or a completed state-machine transition. Ruby 3.2.3
|
|
108
|
+
can retain those execution objects through method caches after the logical
|
|
109
|
+
operation ends. Separating their temporary references from session ownership
|
|
110
|
+
allows the released child to be collected without changing the Ruby or
|
|
111
|
+
state_machines dependency versions.
|
|
112
|
+
|
|
113
|
+
Completed synchronous `ExecutionChange` objects can also remain reachable from
|
|
114
|
+
an idle worker's Fiber/stack, including on the Ubuntu build of Ruby 3.2.3. After
|
|
115
|
+
commit and any ambiguous-outcome readback finish, the change drops its live Agent,
|
|
116
|
+
write callback and participant references. It keeps its result and descriptive
|
|
117
|
+
data. This cleanup runs after success or failure, never before the participant's
|
|
118
|
+
commit, evidence collection or recovery guards have finished. A completed change
|
|
119
|
+
remains single-use; it does not grant a new opportunity to commit or replay work.
|
|
120
|
+
|
|
121
|
+
These changes stay inside Agent execution/participation and Engine dispatch
|
|
122
|
+
lifetime; they introduce no new Contract dependency or public API. In particular,
|
|
123
|
+
OffloadPool does not acquire Agent-specific cleanup policy, replace worker
|
|
124
|
+
threads, or create a new Fiber to make collection happen.
|
|
125
|
+
|
|
126
|
+
## Verification
|
|
127
|
+
|
|
128
|
+
Offline tests use real Agent operations and a fixed local LLMAdapter. They cover
|
|
129
|
+
sync/async success and failure, startup and conversion errors, repeated calls,
|
|
130
|
+
single-worker parent Tool dispatch, approval, independent leases, parallel
|
|
131
|
+
children, observer and token cancellation, cleanup failure and retry, shutdown,
|
|
132
|
+
result transforms and collectability while one or several completed handles
|
|
133
|
+
remain live. The original single-child GC assertion is retained unchanged.
|
|
134
|
+
Participation tests retain completed changes while checking collection of their
|
|
135
|
+
execution-only wiring after success, rollback and lost commit response. The
|
|
136
|
+
last case also verifies that readback runs once and returns the committed result.
|
|
137
|
+
Neutral completion tests exercise both boundary orders, duplicate/concurrent
|
|
138
|
+
notifications, cancellation, startup/cleanup errors and invalid handles.
|
|
139
|
+
|
|
140
|
+
These tests do not establish real-provider availability or resolve LM Studio's
|
|
141
|
+
separate PEG output-parse failure.
|
|
@@ -113,8 +113,10 @@ removal is performed.
|
|
|
113
113
|
blocks release.
|
|
114
114
|
|
|
115
115
|
For notifications, finish child processing, drain notifications, then exit the
|
|
116
|
-
Agent scope. `run_once`
|
|
117
|
-
|
|
116
|
+
Agent scope. `run_once` owns a fresh ephemeral Store and an Agent scope. It releases
|
|
117
|
+
the Agent before returning or propagating an invocation failure, using the same
|
|
118
|
+
release and cleanup-error rules. Use an explicit scope when ownership must extend
|
|
119
|
+
past the invocation, for example until final notification delivery has drained.
|
|
118
120
|
|
|
119
121
|
This is P5 (core). P6 migrates application examples and performs cross-repository
|
|
120
122
|
acceptance against the published or pinned candidate core.
|
|
@@ -8,7 +8,11 @@ authorization and any decision to start another attempt.
|
|
|
8
8
|
## Public behavior
|
|
9
9
|
|
|
10
10
|
`Agent::Base#invoke`, `invoke_async`, `stream`, `stream_async` and
|
|
11
|
-
`start_reserved_async` accept `output_schema: nil
|
|
11
|
+
`start_reserved_async` accept `output_schema: nil` and `schema_delivery: nil`.
|
|
12
|
+
`run_once` forwards both options. With a schema, omitted/nil delivery means
|
|
13
|
+
`:native`; explicit `:native` and `:none` are supported. A non-nil delivery without
|
|
14
|
+
a schema, or any other value, raises ArgumentError before admission. The same
|
|
15
|
+
keywords and final-output semantics apply to sync/async and invoke/stream.
|
|
12
16
|
Without a schema the existing free-text behavior remains. With a schema, successful
|
|
13
17
|
`output` and `result` are deeply frozen JSON objects with String keys; `raw_output`
|
|
14
18
|
preserves the model text. Token events are provisional, not validated objects.
|
|
@@ -45,9 +49,21 @@ values and String/Symbol keys, snapshots recursively, and rejects duplicate norm
|
|
|
45
49
|
keys, Ruby objects and cycles. Generic schema interpretation belongs to C17, not
|
|
46
50
|
Tool::Schema, Agent's caller or Generation.
|
|
47
51
|
|
|
48
|
-
##
|
|
52
|
+
## Schema delivery and completion facts
|
|
49
53
|
|
|
50
|
-
|
|
54
|
+
`:native` sends the declared schema to the provider. `:none` omits native schema
|
|
55
|
+
configuration on every turn, including Tool follow-ups, but retains the same
|
|
56
|
+
strict local validation, completion checks, output filters, frozen values and
|
|
57
|
+
OutputError reasons. It does not send JSON mode, rewrite prompts, infer behavior
|
|
58
|
+
from provider/model names, fall back after rejection or regenerate malformed
|
|
59
|
+
output. Applications must already ask for the declared JSON shape in their
|
|
60
|
+
instructions. Tools and their authorization rules remain unchanged.
|
|
61
|
+
|
|
62
|
+
Use `schema_delivery: :none` explicitly where native schema delivery cannot be
|
|
63
|
+
combined with the application's Tools. This does not repair LM Studio PEG errors:
|
|
64
|
+
a provider failure before a usable response remains a provider failure.
|
|
65
|
+
|
|
66
|
+
With `:native`, C09 `LLMAdapter::Request#response_schema` carries the immutable schema; each model
|
|
51
67
|
turn retains it. RubyLLM `with_schema` sends native schema configuration. The
|
|
52
68
|
transport tests cover RubyLLM 2.0.0 OpenAI/Azure Chat Completions and Responses,
|
|
53
69
|
Anthropic, Gemini and Ollama's OpenAI-compatible endpoint. Constraints are compared
|
|
@@ -77,7 +93,7 @@ unknown), refusal and raw_finish_reason metadata. Only normal final completion c
|
|
|
77
93
|
be a structured success. Tool requests are intermediate; they retain existing
|
|
78
94
|
approval, denial, cancellation and recovery behavior. RubyLLM 2.0 loses streamed
|
|
79
95
|
refusal fields in Chunk values; operation-local Protocol subclasses capture these
|
|
80
|
-
raw facts. No SDK global class or shared protocol registry is patched. The SDK's
|
|
96
|
+
raw facts on every stream, independently of native schema delivery. No SDK global class or shared protocol registry is patched. The SDK's
|
|
81
97
|
raw finish information is retained through saved-response recovery.
|
|
82
98
|
|
|
83
99
|
JSON 2.21.2 is the verified minimum for the strict decoding flags and deterministic
|
|
@@ -104,21 +120,28 @@ no arbitrary class name is constantized. Other transport, cancellation, Tool and
|
|
|
104
120
|
internal failures keep their existing behavior. Output failure never triggers an
|
|
105
121
|
automatic regeneration or replay of a completed Tool operation.
|
|
106
122
|
|
|
107
|
-
|
|
108
|
-
at admission. Digest is SHA-256 of UTF-8 JSON generated with recursively sorted keys
|
|
123
|
+
New structured executions store `output_contract` version 2, schema_ref,
|
|
124
|
+
schema_digest and schema_delivery (`"native"` or `"none"`) at admission. Digest is SHA-256 of UTF-8 JSON generated with recursively sorted keys
|
|
109
125
|
(JSON 2.21.2 `sort_keys: true`); the exact same representation is saved in ContentStore.
|
|
110
126
|
This format preserves arbitrary JSON integers and finite floats, including -0.0,
|
|
111
127
|
without applying the narrower Common canonical-domain number rules. Each Context
|
|
112
|
-
Manifest
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
128
|
+
Manifest carries response_schema_ref only for native delivery. The output
|
|
129
|
+
contract always keeps schema_ref, including with `:none`, so local validation
|
|
130
|
+
survives recovery. The digest identifies the schema alone; exact execution identity
|
|
131
|
+
also compares delivery. Resume/approval restore the saved choice, and a reserved
|
|
132
|
+
ID cannot be reused with a different schema or delivery. Generic config/provider
|
|
133
|
+
schema overrides are rejected.
|
|
116
134
|
|
|
117
135
|
Structured success stores JSON result plus raw_output_ref and result_format=json.
|
|
118
136
|
Old records without output_contract remain facts from a free-text execution; no
|
|
119
|
-
schema is added retroactively.
|
|
120
|
-
|
|
121
|
-
|
|
137
|
+
schema is added retroactively. Existing output_contract version 1 is read as
|
|
138
|
+
native delivery and is not rewritten merely by reading. Version-2 contracts use
|
|
139
|
+
AgentExecution envelope 0.3; old/freeform executions retain envelope 0.2. New
|
|
140
|
+
readers accept both explicitly. Older readers reject envelope 0.3, including for
|
|
141
|
+
completed executions, instead of silently losing delivery policy. Deploy compatible
|
|
142
|
+
readers before writing new structured executions; do not downgrade a store that
|
|
143
|
+
contains 0.3 records to older readers. Unknown versions and digest mismatch fail
|
|
144
|
+
closed. Saved LLM responses include finish_reason,
|
|
122
145
|
refusal and metadata; missing finish evidence is unknown, never assumed normal.
|
|
123
146
|
Fresh-runtime and new SQL connection-pool tests verify success, failure and approval
|
|
124
147
|
continuation without replay of confirmed LLM/Tool work. This is a durable-facts
|
|
@@ -126,6 +149,11 @@ claim, not an external side-effect exactly-once guarantee.
|
|
|
126
149
|
|
|
127
150
|
## Generation evaluation
|
|
128
151
|
|
|
152
|
+
`GeneratorVerifier.new` and `.open` accept `schema_delivery: :native` (default) or
|
|
153
|
+
`:none`. One choice applies to both draft and review operations. Their independent
|
|
154
|
+
schemas and C03 evaluation policy remain unchanged; no role-specific flags or
|
|
155
|
+
automatic fallback are introduced.
|
|
156
|
+
|
|
129
157
|
C03 specifies the fixed protocol: draft answer/string, confidence/number in 0..1,
|
|
130
158
|
citations/array; review approved/boolean, score/number in 0..1, feedback/string.
|
|
131
159
|
All outer fields are required and additional properties are forbidden. Default
|
|
@@ -168,6 +196,38 @@ Neither API implicitly cancels work, answers approval, deletes saved data or
|
|
|
168
196
|
closes the application Store. The application decides how unfinished work becomes
|
|
169
197
|
safe before retrying through the cleanup error's scope/pipeline handle.
|
|
170
198
|
|
|
199
|
+
## Single-invocation ownership
|
|
200
|
+
|
|
201
|
+
`Agent.run_once(definition:, input:, context: nil, knowledge: [], on_event: nil,
|
|
202
|
+
**invoke_options, &event_block)` creates a fresh ephemeral Store and owns one
|
|
203
|
+
Agent through `Agent.open_scope`. The definition must be an `Agent::Base`
|
|
204
|
+
subclass. It forwards the input and invocation options, including the output
|
|
205
|
+
schema and delivery selection, and releases the live Agent before returning.
|
|
206
|
+
The application-level Store configuration is not used or changed.
|
|
207
|
+
|
|
208
|
+
Successful cleanup preserves the invocation value or the identical invocation
|
|
209
|
+
exception. A cleanup failure raises `Agent::ScopeCleanupError`: `result` retains
|
|
210
|
+
a successful value, `primary_error` retains an invocation failure, `failures`
|
|
211
|
+
identifies unreleased owners, and `scope.release!` can retry release. It does not
|
|
212
|
+
repeat the invocation, model request or Tool work. Release does not purge records,
|
|
213
|
+
close shared storage or shut down Runtime. No keep-alive option or legacy path
|
|
214
|
+
is provided; use `create`/`load` or an explicit scope for a reusable Agent.
|
|
215
|
+
|
|
216
|
+
The synchronous call cannot run on EventLoop. It rejects that entry before
|
|
217
|
+
creating its Store or Agent. Supplying both `on_event:` and a listener block is
|
|
218
|
+
also rejected before acquisition. The existing listener-error policy is preserved.
|
|
219
|
+
|
|
220
|
+
Approval waits are not completion. The Agent remains owned while the invocation
|
|
221
|
+
waits; the application still decides approval through the existing API. Use
|
|
222
|
+
`approve_async` when another operation is already waiting for the same execution.
|
|
223
|
+
A concurrent synchronous approval or other accepted operation can still own a
|
|
224
|
+
lease when the invocation returns. In that case cleanup reports the busy owner
|
|
225
|
+
through `ScopeCleanupError`; finish the other operation before retrying release.
|
|
226
|
+
Logical cancellation likewise cannot authorize release of physically active work.
|
|
227
|
+
The convenience API neither answers approval nor cancels or waits for unrelated
|
|
228
|
+
work to make cleanup succeed. Applications needing notifications or other work
|
|
229
|
+
after the invocation should express that whole lifetime with `open_scope`.
|
|
230
|
+
|
|
171
231
|
## Dependencies and implementation units
|
|
172
232
|
|
|
173
233
|
- Remove C03 Generation → C17 OutputParser; C03 continues to use public C04 Agent,
|
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
**Refines**: [038-responsibility-based-source-layout](038-responsibility-based-source-layout.md)
|
|
6
6
|
and [040-configuration-default-composition](040-configuration-default-composition.md)
|
|
7
7
|
|
|
8
|
+
The original lifetime-preservation decision below is historical. The current
|
|
9
|
+
`run_once` contract owns a scope and releases its Agent; see
|
|
10
|
+
[single-invocation ownership](../architecture/structured-output-and-scope.md#single-invocation-ownership).
|
|
11
|
+
The composition placement and default-factory boundary remain in force.
|
|
12
|
+
|
|
8
13
|
## Problem
|
|
9
14
|
|
|
10
15
|
`Agent::Base` selected the concrete default with `Persistence.in_memory`.
|
data/docs/getting-started.md
CHANGED
|
@@ -137,6 +137,26 @@ mutable Agent/Execution/Journal state before every LLM or Tool step. A conflicti
|
|
|
137
137
|
external durable write is surfaced as `Phronomy::Persistence::StateConflictError` rather than
|
|
138
138
|
silently merged into the live instance.
|
|
139
139
|
|
|
140
|
+
Read persisted conversation values without interpreting Journal storage:
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
messages = agent.transcript_messages
|
|
144
|
+
messages.each { |message| puts "#{message.role}: #{message.content}" }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`transcript_messages_async` returns a TaskResult with the same frozen array of
|
|
148
|
+
`LLMAdapter::Message` values. For the same durable root, these match
|
|
149
|
+
`result[:messages]`, including Tool calls, Tool results, non-text content and
|
|
150
|
+
opaque metadata. The operation selects root/generation and Journal records in one
|
|
151
|
+
transaction, then restores immutable content off EventLoop. It holds Agent
|
|
152
|
+
ownership until physical completion, including after logical cancellation.
|
|
153
|
+
The synchronous method refuses to wait on EventLoop. Content and integrity
|
|
154
|
+
failures propagate; an unreadable transcript is not an empty conversation.
|
|
155
|
+
|
|
156
|
+
`agent.transcript` remains a JournalRecord view. Knowledge is excluded from the
|
|
157
|
+
conversation. The application still owns authorization, role filtering and
|
|
158
|
+
presentation; reading invokes no LLM or Tool and changes no durable state.
|
|
159
|
+
|
|
140
160
|
The active transcript and Knowledge views can be advanced independently without
|
|
141
161
|
deleting the append-only canonical Journal:
|
|
142
162
|
|
|
@@ -379,6 +399,12 @@ Agent-backed Tools return control to EventLoop while the child lifecycle is
|
|
|
379
399
|
waiting. They do not occupy an OffloadPool worker merely to wait for a child
|
|
380
400
|
Agent result.
|
|
381
401
|
|
|
402
|
+
Each call owns and releases its fresh child Agent after physical work finishes.
|
|
403
|
+
Normal completion includes that release; a failed release preserves the result,
|
|
404
|
+
original error and child in `Tools::Agent::CleanupError`. See
|
|
405
|
+
[child lifetime and cleanup](architecture/agent-tool-child-lifetime.md) for
|
|
406
|
+
cancellation, approval and release-only retry rules.
|
|
407
|
+
|
|
382
408
|
## Filters
|
|
383
409
|
|
|
384
410
|
Filters can transform or reject values at Agent boundaries:
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Migrating to Phronomy 0.31
|
|
2
|
+
|
|
3
|
+
Phronomy 0.31 adds explicit schema delivery and durable transcript reads, and
|
|
4
|
+
fixes ownership and release of one-shot and Agent Tool children. Review these
|
|
5
|
+
compatibility boundaries when upgrading from 0.30.1.
|
|
6
|
+
|
|
7
|
+
## Stored executions
|
|
8
|
+
|
|
9
|
+
New structured executions store output-contract version 2 with `schema_delivery`
|
|
10
|
+
and use AgentExecution envelope 0.3. The schema remains saved and locally
|
|
11
|
+
validated even with `schema_delivery: :none`; omitting native schema delivery is
|
|
12
|
+
an explicit application choice, not an automatic fallback or output repair.
|
|
13
|
+
|
|
14
|
+
Deploy compatible readers before writing these records. Older readers reject
|
|
15
|
+
0.3, including completed executions; do not downgrade a store containing them.
|
|
16
|
+
Version 0.31 still reads envelope 0.2 and existing version-1 native output
|
|
17
|
+
contracts without rewriting them merely on read. Existing free-text records do
|
|
18
|
+
not acquire a schema retroactively. See the
|
|
19
|
+
[structured-output persistence contract](../architecture/structured-output-and-scope.md#failure-and-persistence).
|
|
20
|
+
|
|
21
|
+
## One-shot Agent ownership
|
|
22
|
+
|
|
23
|
+
`Agent.run_once` owns its fresh Agent and ephemeral Store through
|
|
24
|
+
`Agent.open_scope` and releases the live Agent before returning. A reusable
|
|
25
|
+
Agent must instead use `create`/`load` or an explicit ownership scope.
|
|
26
|
+
|
|
27
|
+
When release succeeds, the original invocation value or exception is preserved.
|
|
28
|
+
Unsafe or failed release raises `Agent::ScopeCleanupError`, retaining the result,
|
|
29
|
+
primary error, failed owners and scope. Resolve the release condition before
|
|
30
|
+
retrying `error.scope.release!`; this does not repeat model or Tool work.
|
|
31
|
+
Approval waits, physical work, independent leases, retention and recovery still
|
|
32
|
+
prevent unsafe release. No records are purged and no shared Store or Runtime is
|
|
33
|
+
shut down. See [single-invocation ownership](../architecture/structured-output-and-scope.md#single-invocation-ownership).
|
|
34
|
+
|
|
35
|
+
## Agent-backed Tools
|
|
36
|
+
|
|
37
|
+
Both synchronous `execute` and asynchronous `call_async` now invoke the child
|
|
38
|
+
through `invoke_async`. A subclass that overrides only `invoke` no longer
|
|
39
|
+
changes the synchronous Tool path. Move the intended customization to the
|
|
40
|
+
asynchronous entry point and preserve its TaskResult contract.
|
|
41
|
+
|
|
42
|
+
Each call owns its fresh child and ephemeral Store until invocation and physical
|
|
43
|
+
work have finished and safe release completes. Normal return includes release;
|
|
44
|
+
successful output conversion remains unchanged. Waiting does not consume an
|
|
45
|
+
OffloadPool worker solely to join the child.
|
|
46
|
+
|
|
47
|
+
A release failure raises `Phronomy::Tools::Agent::CleanupError` with code
|
|
48
|
+
`tool.agent_cleanup_failed`. It retains `child_agent`, `agent_id`, `result`,
|
|
49
|
+
`primary_error` and `cleanup_error`; the cause is the primary failure when one
|
|
50
|
+
exists, otherwise the release failure. Resolve the release condition, then retry
|
|
51
|
+
`error.child_agent.unload_async` for release only. These live handles are
|
|
52
|
+
process-local and are not reconstructed from saved error diagnostics.
|
|
53
|
+
|
|
54
|
+
Cleanup failures are also diagnosed after observer cancellation and are not
|
|
55
|
+
hidden by ordinary Tool error suppression. Completed runtime wiring no longer
|
|
56
|
+
retains released children through generated callbacks, temporary tracker context
|
|
57
|
+
or completed participation changes. Session ownership, commit/readback ordering,
|
|
58
|
+
physical leases and recovery guards remain unchanged. See the
|
|
59
|
+
[Agent Tool child lifetime contract](../architecture/agent-tool-child-lifetime.md).
|
|
60
|
+
|
|
61
|
+
## Live-provider limits
|
|
62
|
+
|
|
63
|
+
LM Studio/gpt-oss PEG output parsing remains unresolved. Known-error
|
|
64
|
+
identification and one bounded retry do not establish the root cause or prevent
|
|
65
|
+
recurrence. The existing evidence distinguishes a failed call stopped after two
|
|
66
|
+
POSTs from a separate application invocation that succeeded on its first POST;
|
|
67
|
+
it does not show recovery by retry within one call.
|
|
68
|
+
|
|
69
|
+
The DX1/DX2 normal live path, streaming, Tool use and history restoration remain
|
|
70
|
+
unverified in that environment. Source review, offline tests and CI support this
|
|
71
|
+
release; they do not establish live LLM acceptance. See the
|
|
72
|
+
[LM Studio policy and evidence limits](../architecture/lm-studio-output-failures.md).
|
|
@@ -12,12 +12,12 @@ module Phronomy
|
|
|
12
12
|
# - approval notifications share the Agent listener as :approval_required;
|
|
13
13
|
# - Recovery resolution uses resolve/resolve_async.
|
|
14
14
|
module AsyncEventApi
|
|
15
|
-
def start_reserved_async(reservation:, input:, config: {}, output_schema: nil)
|
|
15
|
+
def start_reserved_async(reservation:, input:, config: {}, output_schema: nil, schema_delivery: nil)
|
|
16
16
|
__with_operation(async: true) do
|
|
17
17
|
unless reservation.is_a?(ReservedExecution) && reservation.agent_id == agent_id
|
|
18
18
|
raise ArgumentError, "reservation must belong to this Agent"
|
|
19
19
|
end
|
|
20
|
-
config = __invocation_config(_prepare_output_config(config, nil, output_schema))
|
|
20
|
+
config = __invocation_config(_prepare_output_config(config, nil, output_schema, schema_delivery))
|
|
21
21
|
ExactExecution.start(agent: self, execution_id: reservation.execution_id, input: input,
|
|
22
22
|
config: config.merge(phronomy_reservation: reservation.correlation))
|
|
23
23
|
end
|
|
@@ -54,12 +54,13 @@ module Phronomy
|
|
|
54
54
|
config: {},
|
|
55
55
|
invocation_context: nil,
|
|
56
56
|
output_schema: nil,
|
|
57
|
+
schema_delivery: nil,
|
|
57
58
|
**removed_options,
|
|
58
59
|
&removed_block
|
|
59
60
|
)
|
|
60
61
|
__with_operation do
|
|
61
62
|
_reject_removed_invocation_listener_arguments!(removed_options, removed_block)
|
|
62
|
-
config = _prepare_output_config(config, invocation_context, output_schema)
|
|
63
|
+
config = _prepare_output_config(config, invocation_context, output_schema, schema_delivery)
|
|
63
64
|
_check_event_loop_reentrancy(:invoke, :invoke_async)
|
|
64
65
|
_start_agent_operation(
|
|
65
66
|
input,
|
|
@@ -75,12 +76,13 @@ module Phronomy
|
|
|
75
76
|
config: {},
|
|
76
77
|
invocation_context: nil,
|
|
77
78
|
output_schema: nil,
|
|
79
|
+
schema_delivery: nil,
|
|
78
80
|
**removed_options,
|
|
79
81
|
&removed_block
|
|
80
82
|
)
|
|
81
83
|
__with_operation(async: true) do
|
|
82
84
|
_reject_removed_invocation_listener_arguments!(removed_options, removed_block)
|
|
83
|
-
config = _prepare_output_config(config, invocation_context, output_schema)
|
|
85
|
+
config = _prepare_output_config(config, invocation_context, output_schema, schema_delivery)
|
|
84
86
|
_start_agent_operation(
|
|
85
87
|
input,
|
|
86
88
|
config: config,
|
|
@@ -95,13 +97,14 @@ module Phronomy
|
|
|
95
97
|
config: {},
|
|
96
98
|
invocation_context: nil,
|
|
97
99
|
output_schema: nil,
|
|
100
|
+
schema_delivery: nil,
|
|
98
101
|
**removed_options,
|
|
99
102
|
&removed_block
|
|
100
103
|
)
|
|
101
104
|
__with_operation do
|
|
102
105
|
_reject_removed_invocation_listener_arguments!(removed_options, removed_block)
|
|
103
106
|
listener = _required_stream_listener!(:stream)
|
|
104
|
-
config = _prepare_output_config(config, invocation_context, output_schema)
|
|
107
|
+
config = _prepare_output_config(config, invocation_context, output_schema, schema_delivery)
|
|
105
108
|
_check_event_loop_reentrancy(:stream, :stream_async)
|
|
106
109
|
_start_agent_operation(
|
|
107
110
|
input,
|
|
@@ -117,13 +120,14 @@ module Phronomy
|
|
|
117
120
|
config: {},
|
|
118
121
|
invocation_context: nil,
|
|
119
122
|
output_schema: nil,
|
|
123
|
+
schema_delivery: nil,
|
|
120
124
|
**removed_options,
|
|
121
125
|
&removed_block
|
|
122
126
|
)
|
|
123
127
|
__with_operation(async: true) do
|
|
124
128
|
_reject_removed_invocation_listener_arguments!(removed_options, removed_block)
|
|
125
129
|
listener = _required_stream_listener!(:stream_async)
|
|
126
|
-
config = _prepare_output_config(config, invocation_context, output_schema)
|
|
130
|
+
config = _prepare_output_config(config, invocation_context, output_schema, schema_delivery)
|
|
127
131
|
_start_agent_operation(
|
|
128
132
|
input,
|
|
129
133
|
config: config,
|
|
@@ -206,9 +210,14 @@ module Phronomy
|
|
|
206
210
|
|
|
207
211
|
private
|
|
208
212
|
|
|
209
|
-
def _prepare_output_config(config, invocation_context, schema)
|
|
213
|
+
def _prepare_output_config(config, invocation_context, schema, schema_delivery)
|
|
210
214
|
config = _prepare_invocation_config(config, invocation_context)
|
|
211
|
-
schema.nil?
|
|
215
|
+
if schema.nil?
|
|
216
|
+
raise ArgumentError, "schema_delivery requires output_schema" unless schema_delivery.nil?
|
|
217
|
+
return config
|
|
218
|
+
end
|
|
219
|
+
contract = OutputContract.new(schema, schema_delivery: schema_delivery.nil? ? :native : schema_delivery)
|
|
220
|
+
config.merge(phronomy_output_contract: contract)
|
|
212
221
|
end
|
|
213
222
|
|
|
214
223
|
def _start_agent_operation(input, config:, mode:, listener:)
|