phronomy 0.28.0 → 0.30.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 +96 -1
- data/README.md +32 -6
- data/docs/application-recipes.md +229 -0
- data/docs/architecture/agent-configuration-and-tool-binding.md +8 -0
- data/docs/architecture/agent-iteration-budget.md +81 -0
- data/docs/architecture/dx05-agent-scope.md +126 -0
- data/docs/architecture/dx05-workflow-task-state.md +134 -0
- data/docs/architecture/error-api-review.md +245 -0
- data/docs/architecture/generator-verifier-lifecycle-result.md +126 -0
- data/docs/architecture/generator-verifier-ownership.md +18 -109
- data/docs/architecture/llm-continuation-metadata.md +88 -0
- data/docs/architecture/lm-studio-output-failures.md +111 -0
- data/docs/architecture/r8-unit15.md +3 -0
- data/docs/architecture/r8-unit7.md +2 -1
- data/docs/architecture/structured-output-and-scope.md +183 -0
- data/docs/architecture/tracing.md +22 -4
- data/docs/architecture/transport-error-boundaries.md +103 -0
- data/docs/architecture/trust-acceptance-followup.md +87 -0
- data/docs/failure-reason-codes.md +45 -0
- data/docs/features.md +14 -2
- data/docs/getting-started.md +44 -43
- data/docs/migrations/r8-unit15.md +3 -0
- data/docs/ordered-event-delivery.md +54 -0
- data/docs/workflow-completion.md +92 -0
- data/gemfiles/http_minimum.gemfile +10 -0
- data/lib/phronomy/agent/admission.rb +1 -1
- data/lib/phronomy/agent/agent_not_found_error.rb +16 -0
- data/lib/phronomy/agent/agent_unloaded_error.rb +14 -0
- data/lib/phronomy/agent/api/agent.rb +13 -0
- data/lib/phronomy/agent/async_event_api.rb +121 -88
- data/lib/phronomy/agent/base.rb +82 -44
- data/lib/phronomy/agent/before_llm_input.rb +7 -1
- data/lib/phronomy/agent/context_importer.rb +4 -0
- data/lib/phronomy/agent/execution/agent_invocation.rb +6 -2
- data/lib/phronomy/agent/execution/dispatch_preparation.rb +6 -1
- data/lib/phronomy/agent/execution/exact_execution.rb +20 -9
- data/lib/phronomy/agent/execution/execution_cancellation.rb +1 -1
- data/lib/phronomy/agent/execution/execution_coordinator.rb +79 -18
- data/lib/phronomy/agent/execution/execution_outcome_committer.rb +24 -4
- data/lib/phronomy/agent/execution/execution_registry.rb +5 -1
- data/lib/phronomy/agent/execution/initial_preparation.rb +11 -5
- data/lib/phronomy/agent/execution/invocation_actions.rb +1 -1
- data/lib/phronomy/agent/execution/invocation_transitions.rb +4 -4
- data/lib/phronomy/agent/execution/iteration_budget.rb +35 -0
- data/lib/phronomy/agent/execution/runtime_record_encoder.rb +4 -4
- data/lib/phronomy/agent/execution_observation.rb +5 -5
- data/lib/phronomy/agent/filterable.rb +21 -15
- data/lib/phronomy/agent/lifecycle/lifetime_api.rb +76 -0
- data/lib/phronomy/agent/lifecycle/operation_lease.rb +33 -0
- data/lib/phronomy/agent/lifecycle/operation_result.rb +53 -0
- data/lib/phronomy/agent/lifecycle/ownership_registry.rb +99 -8
- data/lib/phronomy/agent/lifecycle/scope.rb +208 -0
- data/lib/phronomy/agent/lifecycle/scope_cleanup_error.rb +21 -0
- data/lib/phronomy/agent/lifecycle/unload_transition.rb +78 -0
- data/lib/phronomy/agent/output_contract.rb +64 -0
- data/lib/phronomy/agent/output_error.rb +53 -0
- data/lib/phronomy/agent/recovery/recovery_coordinator/continuation.rb +6 -6
- data/lib/phronomy/agent/recovery/recovery_coordinator/installation.rb +8 -7
- data/lib/phronomy/agent/recovery/recovery_coordinator/resolution.rb +14 -18
- data/lib/phronomy/agent/recovery/recovery_coordinator.rb +6 -10
- data/lib/phronomy/agent/recovery/recovery_support.rb +6 -8
- data/lib/phronomy/agent/runtime_binding/engine_session_builder.rb +2 -1
- data/lib/phronomy/agent/runtime_input.rb +15 -4
- data/lib/phronomy/agent/saved_context_reader.rb +2 -1
- data/lib/phronomy/agent/store.rb +10 -2
- data/lib/phronomy/agent/stream_callback_error.rb +2 -2
- data/lib/phronomy/common/error.rb +65 -1
- data/lib/phronomy/common/values/immutable.rb +33 -0
- data/lib/phronomy/content_store/base.rb +7 -2
- data/lib/phronomy/context/assembly.rb +18 -22
- data/lib/phronomy/embeddings/authentication_error.rb +11 -0
- data/lib/phronomy/embeddings/backends/ruby_llm_embeddings.rb +8 -2
- data/lib/phronomy/embeddings/rate_limit_error.rb +11 -0
- data/lib/phronomy/engine/fsm_session.rb +16 -1
- data/lib/phronomy/execution/execution_cancellation_error.rb +2 -2
- data/lib/phronomy/execution/execution_timeout_error.rb +2 -2
- data/lib/phronomy/execution/task_result.rb +7 -2
- data/lib/phronomy/filter/filter_block_error.rb +2 -2
- data/lib/phronomy/generation/generator_verifier/agent_operation.rb +28 -5
- data/lib/phronomy/generation/generator_verifier/agent_result_receiver.rb +14 -27
- data/lib/phronomy/generation/generator_verifier/approval.rb +30 -0
- data/lib/phronomy/generation/generator_verifier/cleanup_error.rb +15 -0
- data/lib/phronomy/generation/generator_verifier/invocation.rb +147 -0
- data/lib/phronomy/generation/generator_verifier/pipeline_state.rb +16 -0
- data/lib/phronomy/generation/generator_verifier/protocol.rb +55 -0
- data/lib/phronomy/generation/generator_verifier/result.rb +121 -0
- data/lib/phronomy/generation/generator_verifier/unaccepted_result_error.rb +14 -0
- data/lib/phronomy/generation/generator_verifier/workflow_builder.rb +13 -11
- data/lib/phronomy/generation/generator_verifier.rb +170 -85
- data/lib/phronomy/integrations/ordered_event_delivery.rb +208 -0
- data/lib/phronomy/llm_adapter/backends/lm_studio.rb +139 -0
- data/lib/phronomy/llm_adapter/backends/ruby_llm.rb +134 -12
- data/lib/phronomy/llm_adapter/backends/structured_response_capture.rb +41 -0
- data/lib/phronomy/llm_adapter/message.rb +4 -0
- data/lib/phronomy/llm_adapter/request.rb +17 -2
- data/lib/phronomy/llm_adapter/response.rb +11 -4
- data/lib/phronomy/llm_adapter/structured_output_unsupported_error.rb +14 -0
- data/lib/phronomy/llm_adapter/transport_error.rb +4 -0
- data/lib/phronomy/multi_agent/persistence/team_execution_repository.rb +5 -5
- data/lib/phronomy/multi_agent/shared_state.rb +1 -0
- data/lib/phronomy/multi_agent/team_busy_error.rb +18 -0
- data/lib/phronomy/multi_agent/team_coordinator.rb +20 -7
- data/lib/phronomy/multi_agent/team_not_found_error.rb +18 -0
- data/lib/phronomy/output_parser/json_schema_parser.rb +113 -0
- data/lib/phronomy/output_parser/parse_error.rb +10 -1
- data/lib/phronomy/runtime_composition/agent_defaults.rb +4 -0
- data/lib/phronomy/storage/condition_failed_error.rb +2 -2
- data/lib/phronomy/storage/unique_constraint_error.rb +2 -2
- data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +1 -1
- data/lib/phronomy/tool/base.rb +25 -23
- data/lib/phronomy/tracing/automatic.rb +14 -8
- data/lib/phronomy/tracing/base.rb +37 -7
- data/lib/phronomy/tracing/langfuse_tracer.rb +3 -3
- data/lib/phronomy/tracing/observation.rb +2 -1
- data/lib/phronomy/tracing/open_telemetry_tracer.rb +7 -32
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/workflow/execution/workflow.rb +30 -2
- data/lib/phronomy/workflow/execution/workflow_execution_environment.rb +1 -0
- data/lib/phronomy/workflow/execution/workflow_execution_registry.rb +74 -3
- data/lib/phronomy/workflow/execution/workflow_runner.rb +48 -14
- data/lib/phronomy/workflow/execution/workflow_task_execution.rb +153 -0
- data/lib/phronomy/workflow/execution/workflow_task_reception.rb +72 -0
- data/lib/phronomy/workflow/execution/workflow_task_resume_error.rb +14 -0
- data/lib/phronomy/workflow/runtime_binding/workflow_engine_environment.rb +26 -3
- data/lib/phronomy/workflow/runtime_binding/workflow_phase_machine_builder.rb +3 -1
- data/lib/phronomy/workflow/runtime_binding/workflow_task_session.rb +76 -0
- data/lib/phronomy.rb +7 -2
- data/scripts/api_snapshot.rb +15 -0
- data/scripts/check_readme_ruby.rb +24 -29
- data/scripts/check_readme_runnable.rb +92 -81
- data/scripts/migrate_generator_verifier_result.rb +18 -0
- data/sig/phronomy/agent/agent_not_found_error.rbs +6 -0
- data/sig/phronomy/agent/agent_unloaded_error.rbs +6 -0
- data/sig/phronomy/agent/execution_contract.rbs +4 -3
- data/sig/phronomy/agent/stream_callback_error.rbs +8 -0
- data/sig/phronomy/agent.rbs +36 -5
- data/sig/phronomy/embeddings/base.rbs +6 -0
- data/sig/phronomy/execution/execution.rbs +2 -2
- data/sig/phronomy/extensions.rbs +8 -2
- data/sig/phronomy/filter/filter_block_error.rbs +6 -0
- data/sig/phronomy/generation/generator_verifier.rbs +70 -0
- data/sig/phronomy/integrations.rbs +14 -0
- data/sig/phronomy/llm_adapter/backends/lm_studio.rbs +8 -0
- data/sig/phronomy/llm_adapter/backends/ruby_llm.rbs +1 -0
- data/sig/phronomy/llm_adapter/values.rbs +8 -2
- data/sig/phronomy/multi_agent/team_busy_error.rbs +8 -0
- data/sig/phronomy/multi_agent/team_not_found_error.rbs +8 -0
- data/sig/phronomy/storage/contracts.rbs +2 -2
- data/sig/phronomy/tracing/observation.rbs +1 -1
- data/sig/phronomy/workflow/execution/workflow_execution_environment.rbs +1 -0
- data/sig/phronomy/workflow.rbs +4 -0
- data/sig/phronomy.rbs +6 -0
- metadata +103 -5
- data/lib/phronomy/generation/generator_verifier/default_parser.rb +0 -27
- data/lib/phronomy/generation/low_confidence_error.rb +0 -14
- data/lib/phronomy/runtime_composition/generation_defaults.rb +0 -6
|
@@ -1,111 +1,20 @@
|
|
|
1
1
|
# GeneratorVerifier ownership and event contract
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
| GeneratorVerifier | Public settings and Result; cached Workflow assembly, invocation, default JSON parser fallbacks and final confidence/raise policy. |
|
|
22
|
-
| GeneratorVerifier::WorkflowBuilder | Draft/review/finalize/failed graph, request IDs, prompt creation, Agent startup and convergence guard. |
|
|
23
|
-
| GeneratorVerifier::AgentResultReceiver | Separate draft/review payload conversion, common terminal-event classification and failure notification to the supplied Workflow. |
|
|
24
|
-
| GeneratorVerifier::PipelineState | Existing Workflow fields, request correlation, stale/duplicate rejection and EventLoop-owned state mutation. |
|
|
25
|
-
|
|
26
|
-
The last three constants are private. WorkflowBuilder and AgentResultReceiver
|
|
27
|
-
are new internal classes; PipelineState is the existing class moved with its
|
|
28
|
-
body and canonical name unchanged. The implementation lives in three files under
|
|
29
|
-
generation/generator_verifier. The public facade and Result keep their names and
|
|
30
|
-
locations; no loader changes or compatibility aliases are introduced.
|
|
31
|
-
|
|
32
|
-
The initial D08 sketch suggested multi_agent/generator_verifier_workflow.rb.
|
|
33
|
-
Since the prior layout work established generation as this pattern's owner,
|
|
34
|
-
the internal files now stay there instead of creating a second feature owner.
|
|
35
|
-
The new directory adds a module in directory-based analysis, not a public API.
|
|
36
|
-
|
|
37
|
-
WorkflowBuilder#build reads as a graph: named entries, then ordered transitions.
|
|
38
|
-
start_draft/start_review describe request preparation and dispatch. The receiver's
|
|
39
|
-
draft_payload and review_payload retain different meanings. Their common listener
|
|
40
|
-
has only the two internally selected phases; it is not a public configurable
|
|
41
|
-
pipeline. It shares the same terminal/error rules rather than combining response
|
|
42
|
-
formats into an options-driven generic parser.
|
|
43
|
-
|
|
44
|
-
Default parsers remain explicit callables captured by the facade. Clamp and
|
|
45
|
-
citation normalization move to their receiver owner. The old __send__ calls back
|
|
46
|
-
to facade helpers disappear. The existing private Agent event-sink entry point
|
|
47
|
-
is still called via send; changing that cooperation API is outside this scope.
|
|
48
|
-
|
|
49
|
-
## Preserved ordering and failure contract
|
|
50
|
-
|
|
51
|
-
- Each Agent is constructed once, lazily during the first successful Workflow
|
|
52
|
-
build. The cached Workflow and Agent instances are reused. Each invoke gets a
|
|
53
|
-
fresh Workflow context. No locking or new concurrency policy is introduced.
|
|
54
|
-
- Each request gets a new UUID and a merged context before prompt construction.
|
|
55
|
-
The listener captures that request ID and the context's stable Workflow ID.
|
|
56
|
-
Workflow closures see the completed Workflow assignment before any entry runs.
|
|
57
|
-
- Entry actions start asynchronous Agent work and return next_state, never a
|
|
58
|
-
TaskResult. Inline completion remains valid because signal queues a Workflow
|
|
59
|
-
event. Unknown/nonterminal Agent events are ignored.
|
|
60
|
-
- done parses output, normalizes the phase-specific payload and signals the
|
|
61
|
-
matching completed event. StandardError from parsing, normalization or success
|
|
62
|
-
notification is signalled as the corresponding failed event with the same
|
|
63
|
-
exception object. Failure-notification exceptions are not retried or wrapped.
|
|
64
|
-
- error/timeout/cancelled use the payload error or the same phase-specific
|
|
65
|
-
fallback message. approval_required remains a pipeline failure. A false return
|
|
66
|
-
from signal is returned without a retry or a new failure event.
|
|
67
|
-
- PipelineState accepts only the current request ID. It clears the accepted ID,
|
|
68
|
-
increments iteration after draft completion, appends feedback after review,
|
|
69
|
-
and consumes old/duplicate results before any transition. The receiver does
|
|
70
|
-
not mutate context or add its own deduplication; stale done payloads can still
|
|
71
|
-
be parsed before the state rejects their correlated event.
|
|
72
|
-
- Review completes when both the lower normalized score meets threshold and
|
|
73
|
-
approval is literal true, or when the iteration limit has been reached.
|
|
74
|
-
Otherwise it returns to draft and passes the last feedback to its prompt.
|
|
75
|
-
- Final Result trust remains confidence >= threshold. At the iteration limit,
|
|
76
|
-
high confidence with approved=false can therefore still produce trusted=true.
|
|
77
|
-
A nonpositive limit still executes one draft/review cycle. These are observed
|
|
78
|
-
existing semantics, not behavior fixes hidden in this extraction.
|
|
79
|
-
|
|
80
|
-
Public initialize/invoke parameters, Result fields/trusted? alias, parser
|
|
81
|
-
fallbacks, score/citation normalization, error messages and PipelineState fields
|
|
82
|
-
remain unchanged. Agent/Workflow/Engine/Storage behavior and SQL are not modified.
|
|
83
|
-
Private clamp/normalize_citations implementations relocate; private overrides are
|
|
84
|
-
not treated as additional supported public APIs.
|
|
85
|
-
|
|
86
|
-
## Verification and trade-off
|
|
87
|
-
|
|
88
|
-
Thirty-nine behavior examples pass unchanged on Refactor 40 and the candidate.
|
|
89
|
-
They cover inline completion, ignored progress, pending Task returns, both phases'
|
|
90
|
-
error/timeout/cancelled/approval failures, parser and notification failures,
|
|
91
|
-
request correlation, duplicate/old callbacks, clamping, literal approval, final
|
|
92
|
-
trust, iteration limits, caching, fresh state and config forwarding. Existing
|
|
93
|
-
delayed-callback and WebMock integration examples cover the real Workflow path.
|
|
94
|
-
|
|
95
|
-
A separate process comparison preserves public methods, parameters, constants,
|
|
96
|
-
Result shape, the private state name/field schema/defaults and Runtime inactivity.
|
|
97
|
-
Stable/Beta snapshots do not list GeneratorVerifier, so this explicit comparison
|
|
98
|
-
supplements those existing gates. Core/integration/examples/real SQLite, type,
|
|
99
|
-
style, annotations and isolated gem checks are included in distribution evidence.
|
|
100
|
-
Live PostgreSQL, live LLM, candidate CI and performance are not run for this scope.
|
|
101
|
-
|
|
102
|
-
The facade is 369 -> 118 lines. Workflow definition is 199 -> 34 lines, with a
|
|
103
|
-
12-line facade assembly method. The three internal files are 112/89/57 lines,
|
|
104
|
-
for 376 total, seven more than before. Two new classes and one moved class cost
|
|
105
|
-
additional files; they give graph, receiver and state independent reading units.
|
|
106
|
-
Directory-level dependency counts increase with that physical partition; cycle
|
|
107
|
-
membership is unchanged. This is responsibility separation, not cycle removal.
|
|
108
|
-
|
|
109
|
-
Refactor 41 is applied and independently verified at core e87eb77f, tree
|
|
110
|
-
14dc8a546a4fd96e19e0713f7480ca2efa3c331a. All nine files and the full tree match.
|
|
111
|
-
R08/D08 is closed. The published diagram is applied41-01. R09 and R10 remain.
|
|
3
|
+
The current contract is
|
|
4
|
+
[GeneratorVerifier: approval, lifetime and result](generator-verifier-lifecycle-result.md).
|
|
5
|
+
|
|
6
|
+
GeneratorVerifier is the public owner of draft/review Agent composition and its
|
|
7
|
+
evaluation Result. AgentOperation delegates to public Agent commands. Invocation
|
|
8
|
+
owns per-call approval routing, cancellation subscriptions and child observations.
|
|
9
|
+
WorkflowBuilder builds the graph; AgentResultReceiver converts results;
|
|
10
|
+
PipelineState applies correlated events; Result owns immutable evaluation facts.
|
|
11
|
+
|
|
12
|
+
Both owned Agents are acquired at pipeline construction and reused until release.
|
|
13
|
+
Workflow contexts are fresh for each invocation. Concurrent use of one pipeline
|
|
14
|
+
is rejected. Approval suspension remains pending and is exposed to the application;
|
|
15
|
+
it is not converted to failure. Tool denial is a tool_rejected Result.
|
|
16
|
+
Handoff is explicitly unsupported instead of silently leaving completion pending.
|
|
17
|
+
|
|
18
|
+
The older R08/D08 and R8 unit15 documents record the historical extraction. Their
|
|
19
|
+
preserved score-only trust, approval-failure and lazy Agent acquisition behavior
|
|
20
|
+
has been superseded by the current contract.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# LLM continuation metadata preservation
|
|
2
|
+
|
|
3
|
+
This correction follows [ADR-066](../decisions/066-llm-and-tool-operation-contracts.md).
|
|
4
|
+
It does not change the LLM/Tool/Agent ownership decision, add a dependency to the
|
|
5
|
+
contract, or teach Agent a provider wire format.
|
|
6
|
+
|
|
7
|
+
## Problem and contract
|
|
8
|
+
|
|
9
|
+
The RubyLLM backend converted SDK responses into Phronomy Response values without
|
|
10
|
+
their reasoning data. A later model turn therefore lost information RubyLLM could
|
|
11
|
+
otherwise replay. Saved assistant records already contained Message metadata, but
|
|
12
|
+
RuntimeInput reconstructed only model_id. Explicit Message imports dropped the
|
|
13
|
+
metadata too. These were separate losses along the same conversation path.
|
|
14
|
+
|
|
15
|
+
C09 Message/Response metadata remains immutable canonical JSON. Backend-owned
|
|
16
|
+
continuation data accompanies its assistant message through a Tool round, Journal
|
|
17
|
+
serialization, recovery and explicit Phronomy-message import. Agent preserves the
|
|
18
|
+
value without inspecting its backend keys. Metadata does not authorize a Tool,
|
|
19
|
+
change context selection, become answer content or become arbitrary SDK options.
|
|
20
|
+
Record/control metadata remains distinct from metadata inside an assistant payload.
|
|
21
|
+
|
|
22
|
+
The concrete RubyLLM backend owns the ruby_llm_continuation namespace, format
|
|
23
|
+
version 1, and conversion of SDK thinking text/signature and raw_reasoning. It
|
|
24
|
+
reconstructs those SDK options only inside the backend. RubyLLM still owns wire
|
|
25
|
+
format rendering; no SDK class or provider registry is patched for this fix.
|
|
26
|
+
No thinking toggle, extra model call, fallback, retry, sampling change or Tool
|
|
27
|
+
execution is introduced. Existing Tool thought_signature behavior is unchanged.
|
|
28
|
+
|
|
29
|
+
Streaming's final Response retains the same continuation data. Public content
|
|
30
|
+
chunks remain answer chunks; this change adds no reasoning-display event API.
|
|
31
|
+
SDK token usage semantics are unchanged.
|
|
32
|
+
|
|
33
|
+
## Replay compatibility
|
|
34
|
+
|
|
35
|
+
The backend records the requested model ID, provider, selected protocol name and
|
|
36
|
+
a SHA-256 of the API base. The endpoint itself and credentials are not stored in
|
|
37
|
+
the envelope. Reuse requires an exact match of this scope and format version.
|
|
38
|
+
An incompatible scope/version raises ConfigurationError before sending the next
|
|
39
|
+
model request. State is neither silently discarded nor sent to a different target.
|
|
40
|
+
Malformed envelopes fail explicitly. Legacy messages without the envelope remain
|
|
41
|
+
usable; missing historical reasoning cannot be reconstructed.
|
|
42
|
+
|
|
43
|
+
This conservative equality check is not a claim that all identical endpoints are
|
|
44
|
+
semantically unchanged, or that different models can never share reasoning. It
|
|
45
|
+
does not detect server runtime/template changes behind one URL, account changes,
|
|
46
|
+
or model replacements behind one model ID. Cross-model/provider reasoning
|
|
47
|
+
conversion is outside this correction.
|
|
48
|
+
|
|
49
|
+
Handoff continues to use its existing context selection policy. If a selected
|
|
50
|
+
assistant message carries this state, a same-scope target can replay it and a
|
|
51
|
+
different-scope RubyLLM target rejects it. Applications requiring transfer across
|
|
52
|
+
incompatible scopes must intentionally select/derive portable context using the
|
|
53
|
+
existing context policy; they must not forge the scope or treat metadata as
|
|
54
|
+
authority. Other backend implementations own their own metadata conventions.
|
|
55
|
+
|
|
56
|
+
## Persistence and compatibility
|
|
57
|
+
|
|
58
|
+
There is no DB schema change, new SDK object in storage, or changed public method
|
|
59
|
+
signature. RuntimeInput restores the canonical metadata Hash and uses historical
|
|
60
|
+
top-level model_id only when metadata does not already contain it. Canonical
|
|
61
|
+
metadata is authoritative if both representations exist. Missing metadata means
|
|
62
|
+
an empty Hash; malformed non-Hash metadata is rejected.
|
|
63
|
+
|
|
64
|
+
ContextImporter preserves explicitly supplied canonical Message metadata. It does
|
|
65
|
+
not inspect an externally supplied RubyLLM object's thinking field; converting
|
|
66
|
+
SDK-specific external histories remains a backend-boundary concern, not a new
|
|
67
|
+
Agent SDK dependency. Signed/provider-shaped values stay unmodified and subject
|
|
68
|
+
to the SDK 2.0.0 representation supported by the existing dependency constraint.
|
|
69
|
+
This is not general preservation of every SDK field (for example server-tool
|
|
70
|
+
raw_content), nor a promise of compatibility with untested providers.
|
|
71
|
+
|
|
72
|
+
## Evidence and acceptance
|
|
73
|
+
|
|
74
|
+
The compatibility regressions are:
|
|
75
|
+
|
|
76
|
+
- spec/phronomy/llm_adapter/continuation_metadata_spec.rb: actual SDK parsing and
|
|
77
|
+
rendered HTTP for text/signature, streaming, Responses encrypted state and
|
|
78
|
+
provider-shaped OpenRouter reasoning; scope/version rejection and legacy input.
|
|
79
|
+
- spec/phronomy/agent/continuation_metadata_spec.rb: real Agent Tool loops,
|
|
80
|
+
subsequent invocation, approval across reconstructed storage/Runtime, Handoff,
|
|
81
|
+
and an independent backend with opaque metadata and no RubyLLM calls.
|
|
82
|
+
- spec/phronomy/agent/ruby_llm_materializer_spec.rb: historical model_id reading.
|
|
83
|
+
|
|
84
|
+
The LM Studio gpt-oss-20b reasoning omission was independently reproduced before
|
|
85
|
+
this change. The prior isolated A/B diagnostic returned HTTP 200 in all four
|
|
86
|
+
trials. This correction therefore does not claim to explain or fix peg-native
|
|
87
|
+
HTTP 400, nor to complete Example 22. Actual model behavior and the entire
|
|
88
|
+
application acceptance remain separate verification stages.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# LM Studio provider output failures
|
|
2
|
+
|
|
3
|
+
## Scope and ownership
|
|
4
|
+
|
|
5
|
+
`Phronomy::LLMAdapter::LMStudio` is an explicitly selected concrete RubyLLM
|
|
6
|
+
backend. It handles rejection of the provider's generated conversation format.
|
|
7
|
+
It does not parse or repair partial model output, change prompts or sampling
|
|
8
|
+
parameters, split Tool/schema requests, or replay Agent/Workflow/Tool work.
|
|
9
|
+
The default `RubyLLM` backend and SDK global configuration remain unchanged.
|
|
10
|
+
|
|
11
|
+
C09 owns the meaning of the public `TransportError` reason code
|
|
12
|
+
`llm.provider_output_parse_failed`. A code does not itself authorize retry.
|
|
13
|
+
The concrete backend owns HTTP-body decoding and its transport policy under
|
|
14
|
+
ADR-011/066. Application composition selects that backend and its retry limit.
|
|
15
|
+
No new Contract or Contract dependency is required.
|
|
16
|
+
|
|
17
|
+
## Recognition and evidence limits
|
|
18
|
+
|
|
19
|
+
Recognition requires a RubyLLM BadRequestError/ServerError with its own HTTP
|
|
20
|
+
response and actual HTTP status 400 or 500. The JSON body must contain `error`
|
|
21
|
+
with the exact engine code 500, type `server_error`, and message:
|
|
22
|
+
|
|
23
|
+
The model produced output that does not match the expected peg-native format
|
|
24
|
+
|
|
25
|
+
The engine object may be direct, or JSON after the exact LM Studio prefix
|
|
26
|
+
`Engine protocol predict stream returned an error: `, carried in an error string
|
|
27
|
+
or the error object's message field. No search through arbitrary strings or
|
|
28
|
+
nested exception causes is performed. A malformed/unknown envelope, missing
|
|
29
|
+
response or different status/type/code/message remains an ordinary SDK failure.
|
|
30
|
+
The original SDK exception remains `cause`; the original HTTP status and body
|
|
31
|
+
are not rewritten. The engine's internal 500 is not treated as HTTP 500.
|
|
32
|
+
|
|
33
|
+
The offline HTTP fixture envelopes are synthetic. Follow-up real LM Studio
|
|
34
|
+
acceptance on 2026-10-24 captured the actual HTTP 400 error body and confirmed
|
|
35
|
+
classification as `llm.provider_output_parse_failed`. The eligible request was
|
|
36
|
+
resent once with identical bytes. After the retry also failed, the adapter
|
|
37
|
+
returned the final TransportError at its own limit; the measurement guard did
|
|
38
|
+
not trigger, and Agent/Workflow work was not replayed. A separate application
|
|
39
|
+
invocation completed initially with HTTP 200. Total: two application invocations
|
|
40
|
+
and three generation POSTs; this is not recovery within the failed invocation.
|
|
41
|
+
|
|
42
|
+
Live error identification and bounded retry are verified. Recovery by retry,
|
|
43
|
+
recovery rate, the PEG root cause and recurrence resolution remain unverified.
|
|
44
|
+
The unrecognized-envelope branch was not observed live. The failing host logs
|
|
45
|
+
contain parser suffixes but do not establish the complete pre-parser token
|
|
46
|
+
stream. Unknown formats fail closed without retry.
|
|
47
|
+
|
|
48
|
+
## Retry policy and configuration
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
context = RubyLLM.context do |config|
|
|
52
|
+
config.openai_api_base = "http://127.0.0.1:1234/v1"
|
|
53
|
+
config.openai_api_key = "lm-studio"
|
|
54
|
+
config.openai_protocol = :chat_completions
|
|
55
|
+
config.request_timeout = 120
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
Phronomy.configure do |config|
|
|
59
|
+
config.llm_adapter = Phronomy::LLMAdapter::LMStudio.new(
|
|
60
|
+
context: context,
|
|
61
|
+
max_parse_retries: 1 # default 0; only 0 or 1 is accepted
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The selected backend snapshots the RubyLLM 2.0 context and disables SDK retries
|
|
67
|
+
within that snapshot. **Ordinary 429/500/network retries are also disabled for
|
|
68
|
+
this backend.** Applications needing the existing SDK transport policy should
|
|
69
|
+
keep the default RubyLLM backend; this selection is deliberately explicit.
|
|
70
|
+
The caller's context and global settings are not modified. The frozen snapshot
|
|
71
|
+
prevents later scalar context changes from increasing the retry budget.
|
|
72
|
+
|
|
73
|
+
Only a recognized parse failure can trigger the optional second attempt. It is
|
|
74
|
+
limited to non-streaming `complete`, provider `openai`, model
|
|
75
|
+
`openai/gpt-oss-20b`, no advertised Tools, no response schema, and no Tool messages
|
|
76
|
+
or pending Tool calls in the supplied history. The same immutable request is
|
|
77
|
+
used to construct a fresh operation-local SDK chat. Total generation POSTs are
|
|
78
|
+
at most two. The one-additional-attempt limit is a conservative policy choice,
|
|
79
|
+
not an empirically established optimum or a reliability guarantee.
|
|
80
|
+
|
|
81
|
+
Streaming is never replayed. Delivered chunks and application callback exception
|
|
82
|
+
identity are preserved. Cancellation is checked before another attempt; existing
|
|
83
|
+
worker lifetime and deadline behavior remain unchanged. A final failure exposes
|
|
84
|
+
the final SDK exception as cause. No successful result is synthesized from an
|
|
85
|
+
error body.
|
|
86
|
+
|
|
87
|
+
## Observation
|
|
88
|
+
|
|
89
|
+
The existing logical `llm.call` span covers the complete adapter operation.
|
|
90
|
+
There is no new trace identity or dependency from C09 to C15. Individual SDK HTTP
|
|
91
|
+
requests continue to use RubyLLM's instrumentation. Every failed adapter attempt
|
|
92
|
+
also emits a safe warning containing attempt number, reason code, actual HTTP
|
|
93
|
+
status when known, and whether a retry was selected; no prompt, raw body or
|
|
94
|
+
credential is included. Therefore a later success does not erase the first
|
|
95
|
+
failure from the diagnostic record.
|
|
96
|
+
|
|
97
|
+
An optional concrete-backend `on_attempt:` callable receives these safe fields
|
|
98
|
+
plus `outcome`. It must support concurrent calls if an adapter is shared and is
|
|
99
|
+
not a durable audit store. Logging/callback failures cannot replace the business
|
|
100
|
+
result or original exception. Applications can use SDK instrumentation when they
|
|
101
|
+
need request correlation or detailed HTTP evidence. Successful response events
|
|
102
|
+
do not invent an HTTP status that the public response does not expose.
|
|
103
|
+
|
|
104
|
+
## Verification
|
|
105
|
+
|
|
106
|
+
Offline contracts use a real loopback TCP HTTP server and the actual
|
|
107
|
+
RubyLLM/Faraday stack. They verify status/cause fidelity, default no retry, exactly
|
|
108
|
+
two POSTs for opt-in recovery/exhaustion, unchanged request JSON, excluded
|
|
109
|
+
requests, cancellation between attempts, callback identity, unknown responses,
|
|
110
|
+
configuration isolation and unchanged default-backend SDK retries. They do not
|
|
111
|
+
invoke a live model.
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# r8 unit15: Generation contract boundaries
|
|
2
2
|
|
|
3
|
+
> Historical contract: parser injection/fallback is superseded by
|
|
4
|
+
> [structured output and scope](../architecture/structured-output-and-scope.md).
|
|
5
|
+
|
|
3
6
|
This responsibility refactor is based on applied unit14, commit
|
|
4
7
|
`97c3d76b35594bb971a60a2932410ddf22e48d3a` (tree
|
|
5
8
|
`c326196212a4ac369294a9ccb936619813e51250`).
|
|
@@ -6,7 +6,8 @@ all of C04, C06 or the overall target complete.
|
|
|
6
6
|
|
|
7
7
|
| Owner | Responsibility after this change |
|
|
8
8
|
| --- | --- |
|
|
9
|
-
| Agent
|
|
9
|
+
| Agent IterationBudget | Logical LLM-call limit over durable AgentExecution records; see [iteration budget](agent-iteration-budget.md) |
|
|
10
|
+
| Agent InvocationTransitions | Phase vocabulary, ordered automatic/external transitions, guard evaluation, automatic-chain safety bound |
|
|
10
11
|
| Agent InvocationActions | Prepared input, LLM dispatch, Tool batch orchestration and result/event interpretation |
|
|
11
12
|
| Agent ExecutionCoordinator / RecoveryCoordinator | Admission, durable preparation, approval, terminal outcome, recovery and causal revision checks |
|
|
12
13
|
| Agent ExecutionEnvironment | Private process-local connection required by the Agent framework; session construction/registration, serial owner and operation submission |
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Structured Agent output and application lifetimes
|
|
2
|
+
|
|
3
|
+
This contract supersedes the parser fallback described in the earlier Generation
|
|
4
|
+
ownership documents. Agent owns the promise made by `output_schema:`; Generation
|
|
5
|
+
owns the draft/review protocol and evaluation; the application owns publication,
|
|
6
|
+
authorization and any decision to start another attempt.
|
|
7
|
+
|
|
8
|
+
## Public behavior
|
|
9
|
+
|
|
10
|
+
`Agent::Base#invoke`, `invoke_async`, `stream`, `stream_async` and
|
|
11
|
+
`start_reserved_async` accept `output_schema: nil`. `run_once` forwards this option.
|
|
12
|
+
Without a schema the existing free-text behavior remains. With a schema, successful
|
|
13
|
+
`output` and `result` are deeply frozen JSON objects with String keys; `raw_output`
|
|
14
|
+
preserves the model text. Token events are provisional, not validated objects.
|
|
15
|
+
The final Task and terminal event are produced only after validation. Output
|
|
16
|
+
filters run before validation and cannot silently break the promised shape.
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
schema = {
|
|
20
|
+
type: "object", properties: {answer: {type: "string"}},
|
|
21
|
+
required: ["answer"], additionalProperties: false
|
|
22
|
+
}
|
|
23
|
+
value = Phronomy::Agent.open_scope do |scope|
|
|
24
|
+
agent = scope.create(AnswerAgent)
|
|
25
|
+
agent.invoke_async("Explain the result", output_schema: schema)
|
|
26
|
+
end
|
|
27
|
+
puts value.fetch(:output).fetch("answer")
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
C17 `OutputParser::JsonSchemaParser.new(schema:).parse(text)` performs strict JSON
|
|
31
|
+
decoding and JSON Schema validation. It does not extract fenced JSON, remove model
|
|
32
|
+
control tokens, coerce numbers, clamp scores or repair missing fields. Duplicate
|
|
33
|
+
keys, comments, invalid escapes, control characters and nonfinite values fail.
|
|
34
|
+
Existing extraction-oriented parsers remain available for their separate purposes;
|
|
35
|
+
they do not define Agent's structured-output guarantee.
|
|
36
|
+
|
|
37
|
+
The supported vocabulary is `$schema`, `$defs`, `$ref`, title, description, type,
|
|
38
|
+
enum, const, anyOf, properties, required, additionalProperties, items, minItems,
|
|
39
|
+
maxItems, minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf,
|
|
40
|
+
minLength, maxLength and pattern. The root must declare type object. Nested nullable
|
|
41
|
+
values and local JSON Pointer references are supported; external/cyclic references,
|
|
42
|
+
unknown keywords, invalid patterns and invalid schemas are rejected before model
|
|
43
|
+
or Tool execution. The dialect is JSON Schema 2020-12. Schema input accepts JSON
|
|
44
|
+
values and String/Symbol keys, snapshots recursively, and rejects duplicate normalized
|
|
45
|
+
keys, Ruby objects and cycles. Generic schema interpretation belongs to C17, not
|
|
46
|
+
Tool::Schema, Agent's caller or Generation.
|
|
47
|
+
|
|
48
|
+
## Native transport and completion facts
|
|
49
|
+
|
|
50
|
+
C09 `LLMAdapter::Request#response_schema` carries the immutable schema; each model
|
|
51
|
+
turn retains it. RubyLLM `with_schema` sends native schema configuration. The
|
|
52
|
+
transport tests cover RubyLLM 2.0.0 OpenAI/Azure Chat Completions and Responses,
|
|
53
|
+
Anthropic, Gemini and Ollama's OpenAI-compatible endpoint. Constraints are compared
|
|
54
|
+
with the actual HTTP payload. SDK removal of root schema/title annotations does not
|
|
55
|
+
change validation. These are offline protocol tests, not certifications of every
|
|
56
|
+
remote model. Known model capability failures and native schema rejection raise
|
|
57
|
+
`LLMAdapter::StructuredOutputUnsupportedError` (`llm.structured_output_unsupported`).
|
|
58
|
+
Unregistered OpenAI-compatible local models may attempt native schema delivery.
|
|
59
|
+
Other providers are rejected explicitly for structured requests until their native
|
|
60
|
+
transport has been verified. There is no downgrade to JSON mode or prompt-only
|
|
61
|
+
instructions. Tool capability is checked too when the model catalog is available;
|
|
62
|
+
remote schema/Tool combination rejection remains a provider failure.
|
|
63
|
+
|
|
64
|
+
LM Studio's HTTP 400 string error `Cannot combine structured output constraints
|
|
65
|
+
with lazy grammar` is a known native schema/Tool rejection. It is classified as
|
|
66
|
+
`StructuredOutputUnsupportedError` with its original cause, for both complete and
|
|
67
|
+
stream operations. Unrelated string errors and requests without a schema retain
|
|
68
|
+
their ordinary transport-error behavior. The adapter neither downgrades schemas
|
|
69
|
+
nor replays requests. RubyLLM's configured transport retry policy is independent.
|
|
70
|
+
The minimum Faraday streaming callback omits the HTTP status, so RubyLLM may
|
|
71
|
+
surface this same exact rejection as `ServerError`; its cause is preserved too.
|
|
72
|
+
That synthetic server status can trigger SDK retries before the adapter receives
|
|
73
|
+
the error. Adapter replay tests disable SDK retries to verify their own boundary.
|
|
74
|
+
|
|
75
|
+
`Response` records finish_reason (stop, tool_calls, length, content_filter, refusal,
|
|
76
|
+
unknown), refusal and raw_finish_reason metadata. Only normal final completion can
|
|
77
|
+
be a structured success. Tool requests are intermediate; they retain existing
|
|
78
|
+
approval, denial, cancellation and recovery behavior. RubyLLM 2.0 loses streamed
|
|
79
|
+
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
|
|
81
|
+
raw finish information is retained through saved-response recovery.
|
|
82
|
+
|
|
83
|
+
JSON 2.21.2 is the verified minimum for the strict decoding flags and deterministic
|
|
84
|
+
schema serialization; the minimum dependency CI pins JSON 2.21.2 and RubyLLM 2.0.0
|
|
85
|
+
alongside the existing minimum HTTP dependencies. Unknown SDK or parser faults
|
|
86
|
+
remain failures, not poor-quality Generation results.
|
|
87
|
+
|
|
88
|
+
## Failure and persistence
|
|
89
|
+
|
|
90
|
+
`Agent::OutputError < Phronomy::Error` exposes execution_id, schema_digest, reason,
|
|
91
|
+
violations and raw_output. Violations contain pointers/keywords without embedding
|
|
92
|
+
model text. Raw text is explicitly available for controlled diagnostics; generic
|
|
93
|
+
error messages do not interpolate it.
|
|
94
|
+
|
|
95
|
+
| Code | Reasons |
|
|
96
|
+
| --- | --- |
|
|
97
|
+
| agent.output_invalid | invalid_json, schema_mismatch, missing_output |
|
|
98
|
+
| agent.output_refused | model_refusal |
|
|
99
|
+
| agent.output_incomplete | length, content_filter, unknown_finish_reason |
|
|
100
|
+
|
|
101
|
+
Agent owns error translation. Parser invalid_json/schema_mismatch become OutputError
|
|
102
|
+
with the original cause. A saved error is restored by its explicit owner codec;
|
|
103
|
+
no arbitrary class name is constantized. Other transport, cancellation, Tool and
|
|
104
|
+
internal failures keep their existing behavior. Output failure never triggers an
|
|
105
|
+
automatic regeneration or replay of a completed Tool operation.
|
|
106
|
+
|
|
107
|
+
Execution metadata stores `output_contract` version 1, schema_ref and schema_digest
|
|
108
|
+
at admission. Digest is SHA-256 of UTF-8 JSON generated with recursively sorted keys
|
|
109
|
+
(JSON 2.21.2 `sort_keys: true`); the exact same representation is saved in ContentStore.
|
|
110
|
+
This format preserves arbitrary JSON integers and finite floats, including -0.0,
|
|
111
|
+
without applying the narrower Common canonical-domain number rules. Each Context
|
|
112
|
+
Manifest retains response_schema_ref, including budget projection and recovery.
|
|
113
|
+
New executions may choose another schema; existing executions cannot replace their
|
|
114
|
+
schema on resume/approval or by reusing a reserved ID with a different digest.
|
|
115
|
+
Generic config/provider schema overrides are rejected.
|
|
116
|
+
|
|
117
|
+
Structured success stores JSON result plus raw_output_ref and result_format=json.
|
|
118
|
+
Old records without output_contract remain facts from a free-text execution; no
|
|
119
|
+
schema is added retroactively. The execution codec stays at 0.2: its extensible
|
|
120
|
+
metadata contains the explicitly versioned output contract. Unknown output-contract
|
|
121
|
+
versions or digest mismatch fail closed. Saved LLM responses include finish_reason,
|
|
122
|
+
refusal and metadata; missing finish evidence is unknown, never assumed normal.
|
|
123
|
+
Fresh-runtime and new SQL connection-pool tests verify success, failure and approval
|
|
124
|
+
continuation without replay of confirmed LLM/Tool work. This is a durable-facts
|
|
125
|
+
claim, not an external side-effect exactly-once guarantee.
|
|
126
|
+
|
|
127
|
+
## Generation evaluation
|
|
128
|
+
|
|
129
|
+
C03 specifies the fixed protocol: draft answer/string, confidence/number in 0..1,
|
|
130
|
+
citations/array; review approved/boolean, score/number in 0..1, feedback/string.
|
|
131
|
+
All outer fields are required and additional properties are forbidden. Default
|
|
132
|
+
citation objects require source and excerpt strings. `citation_schema:` customizes
|
|
133
|
+
the citation object only; local references are rebased when embedded, without
|
|
134
|
+
rewriting const/enum data. Generic validation remains with Agent/C17.
|
|
135
|
+
|
|
136
|
+
The old draft_parser/review_parser arguments, DefaultParser, ParseError fallback,
|
|
137
|
+
score clamping and Generation parser composition wiring are removed. C03 receives
|
|
138
|
+
validated String-key values and performs domain evaluation only. Invalid output
|
|
139
|
+
returns `Result` with termination_reason=output_failed, interrupted_phase, failure,
|
|
140
|
+
confidence=nil and review_approved=nil. Failed draft clears any previous answer;
|
|
141
|
+
failed review retains the current valid draft. iterations counts successful drafts.
|
|
142
|
+
A valid score of zero can still satisfy a zero threshold; invalid data cannot.
|
|
143
|
+
Normal low-quality output follows the configured evaluation loop as before.
|
|
144
|
+
|
|
145
|
+
`Result#failure` is nil except for output_failed, where it carries the owned error
|
|
146
|
+
diagnostic (without raw_output). Hash/JSON round trips, equality, hashing and freeze
|
|
147
|
+
include it. `Result.from_h` requires every new field; migrate old saved Result JSON
|
|
148
|
+
explicitly with `scripts/migrate_generator_verifier_result.rb OLD.json NEW.json`.
|
|
149
|
+
The script adds failure=nil, validates the result and creates a new file. It does
|
|
150
|
+
not overwrite the source or silently alter invalid data. `raise_if_unaccepted`
|
|
151
|
+
retains the Result and the original OutputError cause in the same process.
|
|
152
|
+
|
|
153
|
+
## Symmetric lifetime rules
|
|
154
|
+
|
|
155
|
+
`Agent.open_scope` and `GeneratorVerifier.open` wait exactly once when the direct
|
|
156
|
+
block return is a TaskResult. They then attempt all releases. Other values, arrays
|
|
157
|
+
and nested Tasks are not recursively awaited. Failure/cancellation is re-raised
|
|
158
|
+
with the same identity if cleanup succeeds. Double failure retains primary_error,
|
|
159
|
+
all cleanup failures, the scope/pipeline recovery handle and the primary cause.
|
|
160
|
+
`result` contains only a resolved successful value, never a failed Task.
|
|
161
|
+
|
|
162
|
+
`Scope#release!` and GeneratorVerifier release retain failed owners for retry and
|
|
163
|
+
remove successful ones. Return true on complete release, false after release.
|
|
164
|
+
Partial release rejects new work/acquisition. Concurrent Scope release raises
|
|
165
|
+
agent.scope_busy; interruption of a releasing call leaves a retryable failed state.
|
|
166
|
+
Materialization receipts are drained to physical completion before release.
|
|
167
|
+
Neither API implicitly cancels work, answers approval, deletes saved data or
|
|
168
|
+
closes the application Store. The application decides how unfinished work becomes
|
|
169
|
+
safe before retrying through the cleanup error's scope/pipeline handle.
|
|
170
|
+
|
|
171
|
+
## Dependencies and implementation units
|
|
172
|
+
|
|
173
|
+
- Remove C03 Generation → C17 OutputParser; C03 continues to use public C04 Agent,
|
|
174
|
+
C02 Workflow and C13 Execution contracts.
|
|
175
|
+
- Add C04 Agent → C17 OutputParser as a default implementation composed at boot.
|
|
176
|
+
C04 owns the output promise and error; C17 owns syntax/schema interpretation.
|
|
177
|
+
- C09 transports schema/final-response facts and remains independent of C17/C03.
|
|
178
|
+
- No new Storage, Engine or Runtime-private dependency from Generation is added.
|
|
179
|
+
|
|
180
|
+
The Contract concept/foundation SVGs reflect these two edges. Generation → Agent
|
|
181
|
+
must retain its existing route above OutputParser. Repository source diagrams are
|
|
182
|
+
regenerated by the Architecture tool; conceptual target diagrams remain distinct
|
|
183
|
+
from the complete source graph.
|
|
@@ -162,9 +162,9 @@ This guarantee is specific to Framework automatic instrumentation. It does not
|
|
|
162
162
|
redefine the behavior of Application code that directly calls the public
|
|
163
163
|
`Tracing::Base#trace` template method.
|
|
164
164
|
|
|
165
|
-
## 10.
|
|
165
|
+
## 10. Observed token counts
|
|
166
166
|
|
|
167
|
-
`
|
|
167
|
+
Tracing receives a plain `Hash[Symbol, Integer?]` in `usage:`. Supported keys are:
|
|
168
168
|
|
|
169
169
|
```text
|
|
170
170
|
input
|
|
@@ -173,8 +173,26 @@ cached
|
|
|
173
173
|
cache_creation
|
|
174
174
|
```
|
|
175
175
|
|
|
176
|
-
A backend may export the subset its telemetry schema supports.
|
|
177
|
-
|
|
176
|
+
A backend may export the subset its telemetry schema supports. Missing/unknown
|
|
177
|
+
counts remain nil and zero remains a measurement. Tracing does not accept or
|
|
178
|
+
interpret a domain-specific usage object. The LLM domain retains ownership of
|
|
179
|
+
its immutable usage value and aggregation rules; its caller projects the value
|
|
180
|
+
with `to_h` before sending it to Tracing. Agent owns that projection for both
|
|
181
|
+
`llm.call` and `agent.execution`. Arbitrary Workflow/Tool results are not inspected
|
|
182
|
+
for a `usage` field by Tracing.
|
|
183
|
+
|
|
184
|
+
For manual tracing, change `[result, usage]` to `[result, usage&.to_h]` when the
|
|
185
|
+
second value is a domain object. Custom exporters read `usage[:input]` and
|
|
186
|
+
`usage[:output]` instead of calling methods on the domain object. No compatibility
|
|
187
|
+
conversion inside Tracing is retained: it would preserve the dependency being
|
|
188
|
+
removed. The `usage:` keyword, span lifecycle and external telemetry field names
|
|
189
|
+
are unchanged.
|
|
190
|
+
|
|
191
|
+
The internal automatic task observer accepts an owner-supplied projection. A
|
|
192
|
+
failed projection is diagnostic-only: it records no usage and does not change
|
|
193
|
+
the operation result or prevent the span from finishing. Tracing has no static
|
|
194
|
+
dependency on LLMAdapter. This does not assign LLM retry policy to Tracing or to
|
|
195
|
+
the common LLM contract.
|
|
178
196
|
|
|
179
197
|
## 11. Backend notes
|
|
180
198
|
|