phronomy 0.22.0 → 0.24.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/.mutant.yml +3 -4
- data/CHANGELOG.md +215 -10
- data/CONTRIBUTING.md +81 -9
- data/README.md +15 -6
- data/VERIFY.sh +587 -0
- data/benchmark/bench_agent_invoke.rb +2 -2
- data/benchmark/bench_context_assembler.rb +39 -68
- data/benchmark/bench_regression.rb +2 -2
- data/docs/architecture/agent-context.md +174 -0
- data/docs/architecture/before-llm-input.md +78 -0
- data/docs/architecture/context-management.md +232 -0
- data/docs/architecture/knowledge-and-rag.md +130 -0
- data/docs/architecture/multi-agent-handoff.md +147 -0
- data/docs/architecture/persistence.md +186 -0
- data/docs/architecture/removed/agent-context.md +72 -0
- data/docs/architecture/security-boundaries.md +173 -0
- data/docs/architecture/tracing.md +194 -0
- data/docs/architecture.md +89 -0
- data/docs/archive/design/archived/04_api_design.md +507 -0
- data/docs/archive/design/archived/09_guardrails.md +186 -0
- data/docs/archive/design/archived/17_rails_integration.md +175 -0
- data/docs/archive/design/historical/00_design_philosophy.md +122 -0
- data/docs/archive/design/historical/01_rubyllm_evaluation.md +178 -0
- data/docs/archive/design/historical/06_design_decisions.md +143 -0
- data/docs/changelog/0.14-and-earlier.md +1 -1
- data/docs/decisions/001-rubyllm-as-provider-layer.md +6 -1
- data/docs/decisions/002-workflow-context-immutability.md +26 -1
- data/docs/decisions/006-no-built-in-guardrails.md +2 -1
- data/docs/decisions/012-canonical-execution-log-and-context-policy.md +120 -38
- data/docs/decisions/014-unified-persistence-durable-state.md +9 -2
- data/docs/decisions/016-semantic-multi-agent-handoff.md +114 -0
- data/docs/decisions/017-design-authority-and-adr-governance.md +200 -0
- data/docs/decisions/018-durability-guarantees-and-failure-model.md +488 -0
- data/docs/decisions/019-filter-contract-and-security-boundaries.md +229 -0
- data/docs/decisions/020-canonical-workflow-instance-identity.md +177 -0
- data/docs/decisions/021-generic-agent-invocation-identity-removal.md +119 -0
- data/docs/decisions/022-agent-execution-parent-identity-and-runtime-routing-boundary.md +193 -0
- data/docs/decisions/023-fsm-session-incarnation-identity-and-routing.md +139 -0
- data/docs/decisions/024-event-loop-single-writer-agent-runtime.md +188 -0
- data/docs/decisions/025-process-local-agent-ownership-and-runtime-admission.md +249 -0
- data/docs/decisions/026-workflow-runtime-admission-and-durable-terminal-barrier.md +257 -0
- data/docs/decisions/027-llm-adapter-provider-boundary.md +93 -0
- data/docs/decisions/028-preparing-recovery-replay-contract.md +106 -0
- data/docs/decisions/029-semantic-completion-and-application-effect-boundary.md +220 -0
- data/docs/decisions/030-agent-handoff-domain-and-durable-responsibility.md +235 -0
- data/docs/decisions/031-durable-multi-agent-coordination.md +301 -0
- data/docs/decisions/README.md +176 -0
- data/docs/design/durable-semantic-coordination/CHANGELOG_V2_REVISION_2.md +33 -0
- data/docs/design/durable-semantic-coordination/CONTINUATION_DECISION_REFACTOR.md +191 -0
- data/docs/design/durable-semantic-coordination/IMPLEMENTATION_DESIGN_V2.md +862 -0
- data/docs/design/durable-semantic-coordination/IMPLEMENTATION_REPORT.md +106 -0
- data/docs/design/durable-semantic-coordination/RECOVERY_CONTRACT_CLARIFICATIONS.md +179 -0
- data/docs/design/durable-semantic-coordination/RESPONSIBILITY_BOUNDARY_REVIEW.md +302 -0
- data/docs/features.md +65 -11
- data/docs/getting-started.md +77 -45
- data/docs/migrations/0.19.md +14 -7
- data/docs/migrations/0.22.md +390 -0
- data/docs/migrations/durable-semantic-coordination-v2.md +65 -0
- data/docs/persistence-backends.md +129 -40
- data/docs/runtime-and-concurrency.md +227 -33
- data/examples/README.md +13 -0
- data/lib/phronomy/agent/agent_execution.rb +21 -17
- data/lib/phronomy/agent/agent_invocation.rb +288 -93
- data/lib/phronomy/agent/agent_invocation_session_builder.rb +236 -202
- data/lib/phronomy/agent/agent_root.rb +3 -3
- data/lib/phronomy/agent/approval_evaluation_request.rb +37 -19
- data/lib/phronomy/agent/async_event_api.rb +162 -72
- data/lib/phronomy/agent/base.rb +416 -181
- data/lib/phronomy/agent/concerns/before_llm_input.rb +1 -1
- data/lib/phronomy/agent/context_assembler.rb +437 -178
- data/lib/phronomy/agent/context_candidate_resolver.rb +2 -2
- data/lib/phronomy/agent/context_plan.rb +18 -13
- data/lib/phronomy/agent/context_plan_validator.rb +246 -88
- data/lib/phronomy/agent/context_policies/default.rb +123 -34
- data/lib/phronomy/agent/context_policy.rb +109 -3
- data/lib/phronomy/agent/context_policy_input.rb +244 -0
- data/lib/phronomy/agent/context_policy_input_builder.rb +241 -0
- data/lib/phronomy/agent/exact_execution.rb +153 -0
- data/lib/phronomy/agent/execution_cancellation.rb +25 -0
- data/lib/phronomy/agent/execution_coordinator.rb +2417 -572
- data/lib/phronomy/agent/handoff.rb +43 -0
- data/lib/phronomy/agent/handoff_capability_factory.rb +45 -0
- data/lib/phronomy/agent/handoff_context.rb +120 -0
- data/lib/phronomy/agent/handoff_execution_coordinator.rb +143 -0
- data/lib/phronomy/agent/handoff_policy.rb +143 -0
- data/lib/phronomy/agent/handoff_projection.rb +207 -0
- data/lib/phronomy/agent/handoff_request.rb +45 -0
- data/lib/phronomy/agent/handoff_runner.rb +178 -0
- data/lib/phronomy/agent/handoff_state.rb +43 -0
- data/lib/phronomy/agent/journal_record.rb +17 -4
- data/lib/phronomy/agent/llm_input_build_context.rb +1 -1
- data/lib/phronomy/agent/llm_input_manifest.rb +277 -2
- data/lib/phronomy/agent/llm_operation_result.rb +12 -7
- data/lib/phronomy/agent/phase_machine_builder.rb +19 -7
- data/lib/phronomy/agent/provider_call_outcome.rb +23 -7
- data/lib/phronomy/agent/recovery_coordinator/continuation.rb +174 -0
- data/lib/phronomy/agent/recovery_coordinator/installation.rb +381 -0
- data/lib/phronomy/agent/recovery_coordinator/resolution.rb +511 -0
- data/lib/phronomy/agent/recovery_coordinator.rb +217 -0
- data/lib/phronomy/agent/recovery_support.rb +504 -0
- data/lib/phronomy/agent/ruby_llm_materializer.rb +16 -13
- data/lib/phronomy/agent/selection/candidate.rb +53 -0
- data/lib/phronomy/agent/selection/constraint.rb +49 -0
- data/lib/phronomy/agent/shared_state.rb +38 -1
- data/lib/phronomy/agent/tool_approval_request.rb +33 -5
- data/lib/phronomy/agent/tool_definition_set.rb +49 -3
- data/lib/phronomy/agent/tool_invocation.rb +339 -103
- data/lib/phronomy/agent/tool_invocation_session_builder.rb +49 -45
- data/lib/phronomy/agent.rb +20 -2
- data/lib/phronomy/agent_already_exists_error.rb +5 -0
- data/lib/phronomy/agent_purged_error.rb +5 -0
- data/lib/phronomy/engine/concurrency/offload_pool.rb +17 -3
- data/lib/phronomy/engine/concurrency/physical_completion_task.rb +135 -0
- data/lib/phronomy/engine/event_loop.rb +622 -63
- data/lib/phronomy/engine/fsm_session.rb +194 -21
- data/lib/phronomy/engine/runtime/agent_ownership_registry.rb +352 -0
- data/lib/phronomy/engine/runtime/team_ownership_registry.rb +77 -0
- data/lib/phronomy/engine/runtime.rb +92 -20
- data/lib/phronomy/generator_verifier.rb +12 -14
- data/lib/phronomy/invocation_context.rb +9 -29
- data/lib/phronomy/multi_agent/admission_registry.rb +51 -0
- data/lib/phronomy/multi_agent/durable_subagent_coordinator.rb +134 -0
- data/lib/phronomy/multi_agent/fan_out_invocation.rb +24 -33
- data/lib/phronomy/multi_agent/fan_out_session_builder.rb +12 -19
- data/lib/phronomy/multi_agent/orchestrator.rb +71 -26
- data/lib/phronomy/multi_agent/team_coordinator.rb +473 -125
- data/lib/phronomy/multi_agent/team_execution.rb +44 -0
- data/lib/phronomy/multi_agent/team_root.rb +41 -0
- data/lib/phronomy/persistence/durable_codec.rb +706 -0
- data/lib/phronomy/persistence/durable_record.rb +117 -0
- data/lib/phronomy/persistence/in_memory.rb +471 -133
- data/lib/phronomy/persistence/migration/initial_format_migration.rb +226 -0
- data/lib/phronomy/persistence/repository_facades.rb +535 -0
- data/lib/phronomy/persistence.rb +174 -40
- data/lib/phronomy/recovery.rb +186 -0
- data/lib/phronomy/testing/persistence_contract/a_journal_repository.rb +2 -2
- data/lib/phronomy/testing/persistence_contract/a_persistence_backend.rb +2 -1
- data/lib/phronomy/testing/persistence_contract/a_workflow_state_repository.rb +19 -19
- data/lib/phronomy/testing/persistence_contract/an_agent_repository.rb +3 -3
- data/lib/phronomy/testing/persistence_contract/an_execution_repository.rb +5 -5
- data/lib/phronomy/testing/persistence_contract/coordination_repositories.rb +137 -0
- data/lib/phronomy/testing/persistence_contract.rb +5 -0
- data/lib/phronomy/tools/agent.rb +1 -1
- data/lib/phronomy/tracing/automatic.rb +176 -0
- data/lib/phronomy/tracing/base.rb +11 -2
- data/lib/phronomy/tracing/langfuse_tracer.rb +20 -12
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/workflow.rb +3 -6
- data/lib/phronomy/workflow_context.rb +14 -5
- data/lib/phronomy/workflow_recovery.rb +123 -0
- data/lib/phronomy/workflow_runner.rb +468 -256
- data/lib/phronomy.rb +6 -0
- data/scripts/api_snapshot.rb +12 -0
- data/sig/phronomy/agent.rbs +209 -7
- data/sig/phronomy/handoff.rbs +41 -0
- data/sig/phronomy/multi_agent.rbs +35 -0
- data/sig/phronomy/persistence.rbs +123 -4
- data/sig/phronomy/runtime.rbs +1 -4
- data/sig/phronomy/workflow.rbs +2 -2
- data/sig/phronomy.rbs +10 -0
- metadata +84 -18
- data/examples/workflows/agent_event_mapping.rb +0 -101
- data/examples/workflows/generic_task_event_mapping.rb +0 -66
- data/lib/phronomy/agent/activation_registry.rb +0 -28
- data/lib/phronomy/agent/agent_execution_activation.rb +0 -172
- data/lib/phronomy/agent/context_candidate.rb +0 -47
- data/lib/phronomy/agent/context_parts/budget/token_budget_packer.rb +0 -53
- data/lib/phronomy/agent/context_parts/requirements/required_context_resolver.rb +0 -56
- data/lib/phronomy/agent/context_parts/selectors/recent_first_selector.rb +0 -30
- data/lib/phronomy/agent/context_parts/unit_builders/dependency_aware_unit_builder.rb +0 -118
- data/lib/phronomy/agent/context_policy_descriptor.rb +0 -49
- data/lib/phronomy/agent/context_policy_registry.rb +0 -46
- data/lib/phronomy/agent/context_request.rb +0 -35
- data/lib/phronomy/agent/context_selection_unit.rb +0 -38
- data/lib/phronomy/agent/derived_content_spec.rb +0 -34
- data/lib/phronomy/agent/runner.rb +0 -97
- data/lib/phronomy/multi_agent/handoff.rb +0 -64
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# ADR-028: Replay-safe recovery for durably admitted `:preparing` Agent executions
|
|
2
|
+
|
|
3
|
+
Status: Accepted
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
ADR-018 defines Agent execution resumption as continuation of the same logical
|
|
8
|
+
`execution_id` from durable recovery state when there is no unresolved semantic
|
|
9
|
+
operation that prevents safe continuation. The implementation already persists a
|
|
10
|
+
new `AgentExecution` in `status: :preparing, phase: :preparing` before input
|
|
11
|
+
filters, `before_llm_input`, Context Policy, and Manifest finalization run.
|
|
12
|
+
|
|
13
|
+
Before this decision, process loss in that window left a durable active execution
|
|
14
|
+
with no automatic continuation path.
|
|
15
|
+
|
|
16
|
+
## Decision
|
|
17
|
+
|
|
18
|
+
A `:preparing` execution may be resumed automatically only when the framework
|
|
19
|
+
durably recorded `preparation_replayable == true`. Missing, false, or unsupported
|
|
20
|
+
values fail closed. The recovered continuation keeps the same `execution_id`.
|
|
21
|
+
|
|
22
|
+
The preparation region is **replay-safe, not deterministic**. Input filtering,
|
|
23
|
+
`before_llm_input`, Context Policy, retrieval, and other preparation work may be
|
|
24
|
+
executed again after process loss. Applications must ensure those callbacks are
|
|
25
|
+
safe under at-least-once execution. The framework does not require repeated
|
|
26
|
+
preparation to produce byte-identical results.
|
|
27
|
+
|
|
28
|
+
Before Manifest finalization, preparation results may be recomputed. After the
|
|
29
|
+
Manifest is durably committed, the Manifest is the authority and existing
|
|
30
|
+
post-Manifest recovery rules apply.
|
|
31
|
+
|
|
32
|
+
### Durable application context
|
|
33
|
+
|
|
34
|
+
`config` remains a runtime/application Hash and is not generally durable. An
|
|
35
|
+
Application value that affects preparation semantics and must survive restart is
|
|
36
|
+
placed under one reserved key:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
config: {
|
|
40
|
+
durable_context: {
|
|
41
|
+
"tenant" => "A",
|
|
42
|
+
"search_profile" => "legal"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`config[:durable_context]`, when present, must be a Hash accepted by
|
|
48
|
+
`Phronomy::CanonicalJSON`. The framework performs a Canonical JSON round trip
|
|
49
|
+
before execution admission and uses the detached immutable snapshot for both the
|
|
50
|
+
initial run and any recovery replay. `durable_context: nil` and non-Hash values
|
|
51
|
+
are rejected before an `AgentExecution` is created. Missing and explicit `{}` are
|
|
52
|
+
distinct.
|
|
53
|
+
|
|
54
|
+
The snapshot is stored in the Content Store and the execution retains only
|
|
55
|
+
`durable_context_ref`.
|
|
56
|
+
|
|
57
|
+
### Conservative replay eligibility
|
|
58
|
+
|
|
59
|
+
This change intentionally does not add a new arbitrary raw-input serialization
|
|
60
|
+
format. Current initial admission stores `extract_message(input)` as text, while
|
|
61
|
+
filters and instruction construction may inspect the original Ruby input.
|
|
62
|
+
Therefore automatic `:preparing` replay is enabled only for String invocation
|
|
63
|
+
inputs. Non-String inputs fail closed after process loss at this phase.
|
|
64
|
+
|
|
65
|
+
Automatic preparation replay is also disabled when the framework can see a
|
|
66
|
+
Runtime-only semantic dependency that it cannot reconstruct, including:
|
|
67
|
+
|
|
68
|
+
- Multi-Agent handoff/routing wiring;
|
|
69
|
+
- a custom Agent invocation approval policy;
|
|
70
|
+
- invocation-context approval/redaction/token-budget policy values.
|
|
71
|
+
|
|
72
|
+
These conditions are represented by the single durable boolean
|
|
73
|
+
`preparation_replayable`; the framework does not persist those Runtime objects.
|
|
74
|
+
A true value records that no framework-known blocker was present; it does not
|
|
75
|
+
replace the Application replay-safety contract for callbacks or custom config.
|
|
76
|
+
|
|
77
|
+
Applications that use other ordinary `config` entries to influence preparation,
|
|
78
|
+
Provider-adapter, Tool, or other continuation semantics are responsible for moving
|
|
79
|
+
restart-required values into `config[:durable_context]` and reading them from that
|
|
80
|
+
sub-Hash after recovery.
|
|
81
|
+
|
|
82
|
+
### Agent definition compatibility
|
|
83
|
+
|
|
84
|
+
Existing `agent_definition_id` / `agent_definition_version` load validation
|
|
85
|
+
continues to guard runtime definition compatibility. Applications must increment
|
|
86
|
+
the Agent definition version when a change to filters, hooks, Context Policy, or
|
|
87
|
+
other preparation behavior is not recovery-compatible.
|
|
88
|
+
|
|
89
|
+
## Non-goals
|
|
90
|
+
|
|
91
|
+
This decision does not add:
|
|
92
|
+
|
|
93
|
+
- recovery of caller `Task` objects;
|
|
94
|
+
- durable Multi-Agent active routing or Handoff Context;
|
|
95
|
+
- a durable execution-query API;
|
|
96
|
+
- a new Recovery event;
|
|
97
|
+
- Content Store garbage collection;
|
|
98
|
+
- Workflow checkpoint changes;
|
|
99
|
+
- deterministic replay of Application callbacks.
|
|
100
|
+
|
|
101
|
+
## Consequences
|
|
102
|
+
|
|
103
|
+
Direct, replay-safe String invocations can continue the same durable logical
|
|
104
|
+
execution after process loss in the initial preparation window. Unsupported or
|
|
105
|
+
ambiguous cases remain fail-closed rather than being guessed or silently
|
|
106
|
+
abandoned.
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# ADR-029: Semantic Completion and Application Effect Boundary
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted. V2 revision 2, 2026-09-06.
|
|
6
|
+
|
|
7
|
+
User approval covers the V2 boundary and the five recovery-contract clarifications.
|
|
8
|
+
Acceptance is design authority; it is not a claim of repository integration or test success.
|
|
9
|
+
|
|
10
|
+
## Date
|
|
11
|
+
|
|
12
|
+
2026-09-06
|
|
13
|
+
|
|
14
|
+
## Context
|
|
15
|
+
|
|
16
|
+
Phronomy durably records Agent semantic execution progress and terminal outcomes.
|
|
17
|
+
After a terminal durable commit, Runtime may notify Application code through
|
|
18
|
+
`on_event` and settle process-local caller Tasks.
|
|
19
|
+
|
|
20
|
+
A previous proposal attempted to make terminal callback delivery itself durable
|
|
21
|
+
across process loss by storing a pending-delivery descriptor, rediscovering
|
|
22
|
+
pending callbacks on `Agent.load`, and durably acknowledging callback attempts.
|
|
23
|
+
|
|
24
|
+
That approach crossed an architectural boundary.
|
|
25
|
+
|
|
26
|
+
An arbitrary Application callback may perform an external effect outside
|
|
27
|
+
Phronomy's Persistence transaction domain. Phronomy cannot make that effect
|
|
28
|
+
exactly once. A process can die after the callback has produced an effect but
|
|
29
|
+
before Phronomy records an acknowledgement, so retrying the callback still
|
|
30
|
+
requires Application-level idempotency.
|
|
31
|
+
|
|
32
|
+
Maintaining a framework outbox/ACK protocol therefore does not remove the
|
|
33
|
+
Application responsibility that matters most, while it adds persistence indexes,
|
|
34
|
+
recovery ordering, acknowledgement reconciliation and callback-specific state to
|
|
35
|
+
the Agent execution engine.
|
|
36
|
+
|
|
37
|
+
## Decision
|
|
38
|
+
|
|
39
|
+
### 1. Semantic terminal state remains the durable boundary
|
|
40
|
+
|
|
41
|
+
The existing AgentExecution terminal statuses remain semantic terminal states:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
completed
|
|
45
|
+
handed_off
|
|
46
|
+
failed
|
|
47
|
+
cancelled
|
|
48
|
+
rejected
|
|
49
|
+
blocked
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Phronomy does not add a callback-only `:completing` status.
|
|
53
|
+
|
|
54
|
+
A known-successful terminal durable transition ends the logical AgentExecution and
|
|
55
|
+
releases normal Agent admission according to ADR-025.
|
|
56
|
+
|
|
57
|
+
### 2. Terminal semantic result/error evidence is durable
|
|
58
|
+
|
|
59
|
+
The terminal transaction continues to persist the canonical semantic evidence
|
|
60
|
+
already required by Agent durability, including as applicable:
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
terminal AgentExecution
|
|
64
|
+
AgentRoot revision/lifecycle state
|
|
65
|
+
Journal terminal facts
|
|
66
|
+
result_ref / error_ref
|
|
67
|
+
Provider / Tool durable evidence already owned by Agent execution
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Process loss after this commit must not cause the semantic execution to be rerun
|
|
71
|
+
merely because the Application did not observe its completion callback.
|
|
72
|
+
|
|
73
|
+
### 3. `on_event` is a process-local observation contract
|
|
74
|
+
|
|
75
|
+
Application `on_event` callbacks are Runtime observations.
|
|
76
|
+
|
|
77
|
+
Phronomy invokes them in the current process according to the existing callback
|
|
78
|
+
error policy, but does not create a restart-spanning delivery obligation.
|
|
79
|
+
|
|
80
|
+
Phronomy does not persist:
|
|
81
|
+
|
|
82
|
+
```text
|
|
83
|
+
terminal_delivery
|
|
84
|
+
delivery_pending
|
|
85
|
+
callback attempt_count
|
|
86
|
+
callback acknowledgement state
|
|
87
|
+
callback/Proc/Task references
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`Agent.load` does not scan for or redeliver missed terminal callbacks.
|
|
91
|
+
|
|
92
|
+
### 4. Callback loss after process loss is allowed
|
|
93
|
+
|
|
94
|
+
The following failure is explicitly permitted:
|
|
95
|
+
|
|
96
|
+
```text
|
|
97
|
+
terminal semantic commit succeeds
|
|
98
|
+
-> process dies before Application callback
|
|
99
|
+
-> callback is not reconstructed/redelivered
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The semantic outcome remains authoritative and must not be replayed.
|
|
103
|
+
|
|
104
|
+
An Application that requires restart-spanning notification must implement that
|
|
105
|
+
requirement in an Application-owned durable mechanism such as:
|
|
106
|
+
|
|
107
|
+
```text
|
|
108
|
+
outbox
|
|
109
|
+
job queue
|
|
110
|
+
database transaction/status row
|
|
111
|
+
idempotency key
|
|
112
|
+
Application Workflow
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 5. Callback effects are outside Phronomy's exactly-once guarantee
|
|
116
|
+
|
|
117
|
+
Phronomy does not claim exactly-once semantics for:
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
email
|
|
121
|
+
webhook
|
|
122
|
+
external database mutation
|
|
123
|
+
message-broker publication
|
|
124
|
+
arbitrary Application callback side effect
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Applications own idempotency/deduplication appropriate to those systems.
|
|
128
|
+
|
|
129
|
+
### 6. Caller Tasks remain Runtime-only
|
|
130
|
+
|
|
131
|
+
A caller-facing Task can observe same-process success/failure and callback policy.
|
|
132
|
+
It is not rehydrated after process loss.
|
|
133
|
+
|
|
134
|
+
No durable state exists solely to recreate or settle a lost caller Task.
|
|
135
|
+
|
|
136
|
+
### 7. Handoff routing is independent from callback delivery
|
|
137
|
+
|
|
138
|
+
A Source Agent may terminalize as `:handed_off`.
|
|
139
|
+
|
|
140
|
+
Durable Handoff responsibility transfer is governed by ADR-030 and does not depend
|
|
141
|
+
on whether a local `:handoff` Application event was observed.
|
|
142
|
+
|
|
143
|
+
Losing the local callback must not lose the Target routing state.
|
|
144
|
+
|
|
145
|
+
### 8. Read-only outcome access and execution discovery
|
|
146
|
+
|
|
147
|
+
Applications must be able to query an execution's owner, status and durable
|
|
148
|
+
result/error by exact semantic execution ID without invoking/recovering it or
|
|
149
|
+
redelivering callbacks. If admission committed before the caller received its ID,
|
|
150
|
+
a public discovery path from the known Agent/Team identity must cover retained
|
|
151
|
+
terminal as well as active executions. Candidate discovery does not guarantee
|
|
152
|
+
request deduplication or unambiguous correlation. Existing retention applies.
|
|
153
|
+
|
|
154
|
+
Reuse existing APIs where they meet this contract; map missing capabilities only
|
|
155
|
+
after baseline inspection. Do not invent a framework outbox or request registry.
|
|
156
|
+
See [RC-01](RECOVERY_CONTRACT_CLARIFICATIONS.md#1-rc-01--確定結果の参照と実行の発見).
|
|
157
|
+
|
|
158
|
+
### 9. Unknown commit outcome and cancellation
|
|
159
|
+
|
|
160
|
+
A failed read is not proof of absence. A lost commit acknowledgement requires
|
|
161
|
+
readback with the same operation/reserved identities before new semantic work.
|
|
162
|
+
Unresolved storage uncertainty follows existing Persistence error/retry rules,
|
|
163
|
+
not Application factual invention (RC-02).
|
|
164
|
+
|
|
165
|
+
Stopping observation, losing a caller, or shutting down Runtime does not by
|
|
166
|
+
itself request semantic cancellation. Explicit semantic cancellation uses the
|
|
167
|
+
existing Agent contract and preserves exact child identities and confirmed
|
|
168
|
+
outcomes through its existing terminal/settlement boundaries (RC-04).
|
|
169
|
+
|
|
170
|
+
The guarantee is reuse of confirmed durable outcomes and recovery of unfinished
|
|
171
|
+
executions under the same semantic identity. Unknown external Provider/Tool
|
|
172
|
+
effects follow existing Agent Recovery; external effects are not exactly once
|
|
173
|
+
(RC-05).
|
|
174
|
+
|
|
175
|
+
## Persistence / Runtime boundary
|
|
176
|
+
|
|
177
|
+
The ordering remains:
|
|
178
|
+
|
|
179
|
+
```text
|
|
180
|
+
EventLoop-owned live authority
|
|
181
|
+
-> OffloadPool durable semantic transaction
|
|
182
|
+
-> Persistence commit
|
|
183
|
+
-> EventLoop apply / release admission
|
|
184
|
+
-> optional current-process Application callback
|
|
185
|
+
-> current-process Task settlement
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
No callback acknowledgement transaction follows.
|
|
189
|
+
|
|
190
|
+
## Relationship to existing ADRs
|
|
191
|
+
|
|
192
|
+
- Clarifies ADR-018 X0 boundaries: Application effects remain external.
|
|
193
|
+
- Preserves ADR-023: Runtime routing identities remain non-durable.
|
|
194
|
+
- Preserves ADR-024: EventLoop remains live-state writer.
|
|
195
|
+
- Preserves ADR-025: AgentExecution lifetime ends at semantic terminal commit.
|
|
196
|
+
- Does not alter ADR-028 preparation replay rules.
|
|
197
|
+
- ADR-030/031 may persist additional **semantic coordination facts**, but not
|
|
198
|
+
restart-spanning Application callback delivery.
|
|
199
|
+
|
|
200
|
+
## Required invariants
|
|
201
|
+
|
|
202
|
+
1. Known terminal semantic work is never replayed merely because a callback may
|
|
203
|
+
have been lost.
|
|
204
|
+
2. No terminal callback-delivery metadata/index/ACK protocol is required.
|
|
205
|
+
3. Callbacks, Tasks and external side effects remain Runtime/Application concerns.
|
|
206
|
+
4. Agent terminal statuses remain semantic states, not notification states.
|
|
207
|
+
5. Phronomy does not claim exactly-once external effects.
|
|
208
|
+
6. Read-only result access and retained execution discovery do not trigger work.
|
|
209
|
+
7. Read/commit uncertainty and observation loss do not authorize replacement work.
|
|
210
|
+
|
|
211
|
+
## Non-goals
|
|
212
|
+
|
|
213
|
+
This ADR does not:
|
|
214
|
+
|
|
215
|
+
- provide restart-spanning callback delivery;
|
|
216
|
+
- provide arbitrary callback-side-effect deduplication;
|
|
217
|
+
- recover caller Tasks;
|
|
218
|
+
- introduce an Application outbox inside Persistence;
|
|
219
|
+
- change Handoff routing semantics;
|
|
220
|
+
- change cross-process ownership guarantees.
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# ADR-030: Agent Handoff Domain and Durable Responsibility
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted. V2 revision 2, 2026-09-06.
|
|
6
|
+
|
|
7
|
+
User approval covers the V2 boundary and the five recovery-contract clarifications.
|
|
8
|
+
Acceptance is design authority; it is not a claim of repository integration or test success.
|
|
9
|
+
|
|
10
|
+
## Date
|
|
11
|
+
|
|
12
|
+
2026-09-06
|
|
13
|
+
|
|
14
|
+
## Partially supersedes
|
|
15
|
+
|
|
16
|
+
`016-semantic-multi-agent-handoff` for:
|
|
17
|
+
|
|
18
|
+
- namespace/domain placement of Handoff;
|
|
19
|
+
- Runtime-local-only active responsibility;
|
|
20
|
+
- allowance for independent Source/Target Persistence domains in the durable
|
|
21
|
+
Handoff path; and
|
|
22
|
+
- the public Handoff Runner namespace.
|
|
23
|
+
|
|
24
|
+
ADR-016 remains the historical rationale and remains authoritative for the
|
|
25
|
+
Source-to-Target semantic transfer model, HandoffPolicy category semantics,
|
|
26
|
+
Context dependency grouping, immutable transferred Context, Target ContextPolicy
|
|
27
|
+
ownership, and provenance rules except where this ADR explicitly changes them.
|
|
28
|
+
|
|
29
|
+
## Context
|
|
30
|
+
|
|
31
|
+
ADR-016 intentionally made active Handoff responsibility Runtime-local. Process
|
|
32
|
+
reset therefore restarted responsibility at the main Agent.
|
|
33
|
+
|
|
34
|
+
That behavior is insufficient for a framework-owned Handoff abstraction once the
|
|
35
|
+
Source execution has durably committed `:handed_off`: process loss must not force
|
|
36
|
+
Source semantic work to run again merely to rediscover the Target.
|
|
37
|
+
|
|
38
|
+
This is a framework semantic-routing concern, not an Application callback concern.
|
|
39
|
+
ADR-029 therefore does not make Handoff durability depend on restart-spanning
|
|
40
|
+
notification delivery.
|
|
41
|
+
|
|
42
|
+
## Decision
|
|
43
|
+
|
|
44
|
+
### 1. Handoff moves to the Agent domain
|
|
45
|
+
|
|
46
|
+
Public types move as a clean break:
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
Phronomy::MultiAgent::Handoff
|
|
50
|
+
-> Phronomy::Agent::Handoff
|
|
51
|
+
|
|
52
|
+
Phronomy::MultiAgent::HandoffPolicy
|
|
53
|
+
-> Phronomy::Agent::HandoffPolicy
|
|
54
|
+
|
|
55
|
+
Phronomy::MultiAgent::Runner
|
|
56
|
+
-> Phronomy::Agent::HandoffRunner
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Handoff-specific private types move under the Agent Handoff implementation
|
|
60
|
+
boundary.
|
|
61
|
+
|
|
62
|
+
No compatibility alias is required by this ADR.
|
|
63
|
+
|
|
64
|
+
### 2. `main_agent.agent_id` is the durable routing anchor
|
|
65
|
+
|
|
66
|
+
No generic coordination/thread/session identity is introduced.
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
main_agent.agent_id
|
|
70
|
+
= durable Handoff routing anchor
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
One durable HandoffState is keyed by that identity.
|
|
74
|
+
|
|
75
|
+
### 3. Persistence adds `handoff_states`
|
|
76
|
+
|
|
77
|
+
The durable state contains semantic routing facts only:
|
|
78
|
+
|
|
79
|
+
```text
|
|
80
|
+
main_agent_id
|
|
81
|
+
handoff_revision
|
|
82
|
+
active_agent_id
|
|
83
|
+
active_handoff_context_ref
|
|
84
|
+
phase
|
|
85
|
+
pending_source_execution_id
|
|
86
|
+
pending_target_execution_id
|
|
87
|
+
created_at
|
|
88
|
+
updated_at
|
|
89
|
+
metadata
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
It never stores Agent instances, Agent classes, Procs, HandoffPolicy objects,
|
|
93
|
+
Tasks, FSMSessions or EventLoop routing state.
|
|
94
|
+
|
|
95
|
+
### 4. One durable Handoff graph uses one Persistence domain
|
|
96
|
+
|
|
97
|
+
The main/source/target Agents and HandoffState must use the same
|
|
98
|
+
`Phronomy::Persistence` transaction domain.
|
|
99
|
+
|
|
100
|
+
A graph requiring a distributed transaction across independent Persistence
|
|
101
|
+
domains is rejected before semantic work.
|
|
102
|
+
|
|
103
|
+
### 5. Source terminalization and responsibility transfer are one semantic transaction
|
|
104
|
+
|
|
105
|
+
When a Source chooses a valid Handoff, Phronomy:
|
|
106
|
+
|
|
107
|
+
1. resolves the current finalized Source Manifest;
|
|
108
|
+
2. applies HandoffPolicy projection;
|
|
109
|
+
3. materializes immutable HandoffContext;
|
|
110
|
+
4. reserves the exact Target `execution_id`;
|
|
111
|
+
5. commits Source `:handed_off` and HandoffState transfer atomically.
|
|
112
|
+
|
|
113
|
+
The transaction records at least:
|
|
114
|
+
|
|
115
|
+
```text
|
|
116
|
+
Source AgentExecution -> :handed_off
|
|
117
|
+
Source AgentRoot terminal/idle revision
|
|
118
|
+
Source Journal audit facts
|
|
119
|
+
HandoffContext content reference
|
|
120
|
+
HandoffState.active_agent_id -> Target
|
|
121
|
+
HandoffState.active_handoff_context_ref -> transferred Context
|
|
122
|
+
HandoffState.phase -> target_pending
|
|
123
|
+
HandoffState.pending_source_execution_id -> Source execution_id
|
|
124
|
+
HandoffState.pending_target_execution_id -> reserved Target execution_id
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
There is no terminal callback-delivery descriptor in this transaction.
|
|
128
|
+
|
|
129
|
+
### 6. Target execution identity is reserved before Target semantic work
|
|
130
|
+
|
|
131
|
+
Recovery uses the exact reserved Target `execution_id`:
|
|
132
|
+
|
|
133
|
+
```text
|
|
134
|
+
authoritatively absent after a successful read
|
|
135
|
+
-> establish that exact reserved execution only after admission is confirmed
|
|
136
|
+
|
|
137
|
+
nonterminal
|
|
138
|
+
-> recover that exact execution
|
|
139
|
+
|
|
140
|
+
terminal
|
|
141
|
+
-> consume its durable outcome; never create a replacement execution
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The execution ID is semantic Agent identity, not Runtime FSMSession identity.
|
|
145
|
+
|
|
146
|
+
### 7. Active responsibility survives later turns and process loss
|
|
147
|
+
|
|
148
|
+
When a Handoff turn ends normally at Target B, `active_agent_id` remains B.
|
|
149
|
+
|
|
150
|
+
The next HandoffRunner turn starts at B.
|
|
151
|
+
|
|
152
|
+
After process reset, compatible HandoffRunner wiring loads the same HandoffState
|
|
153
|
+
and again starts/resumes from B rather than reverting to the original main Agent.
|
|
154
|
+
|
|
155
|
+
### 8. Multi-hop updates the same HandoffState
|
|
156
|
+
|
|
157
|
+
A -> B -> C updates the original main-Agent-anchored HandoffState.
|
|
158
|
+
|
|
159
|
+
No nested generic coordination IDs are created.
|
|
160
|
+
|
|
161
|
+
### 9. Runtime graph/Policy wiring is Application code
|
|
162
|
+
|
|
163
|
+
HandoffRunner requires the current Application-supplied Handoff graph and Policies
|
|
164
|
+
to reconstruct Runtime behavior.
|
|
165
|
+
|
|
166
|
+
Those Ruby objects are never persisted.
|
|
167
|
+
|
|
168
|
+
If required wiring is absent or incompatible, recovery fails closed instead of:
|
|
169
|
+
|
|
170
|
+
- reverting to main Agent;
|
|
171
|
+
- inventing a graph;
|
|
172
|
+
- blindly replaying Source work.
|
|
173
|
+
|
|
174
|
+
### 10. HandoffContext is durably materializable but not adopted automatically
|
|
175
|
+
|
|
176
|
+
The canonical immutable HandoffContext value is stored in ContentStore and
|
|
177
|
+
referenced by HandoffState/execution metadata.
|
|
178
|
+
|
|
179
|
+
Transferred material remains request-scoped Target Context unless Target
|
|
180
|
+
execution creates its own canonical Journal/Knowledge facts.
|
|
181
|
+
|
|
182
|
+
### 11. Local Application events are Runtime-only
|
|
183
|
+
|
|
184
|
+
A Source may emit a same-process `:handoff` event.
|
|
185
|
+
|
|
186
|
+
That event is not durable routing authority and is not redelivered after restart.
|
|
187
|
+
|
|
188
|
+
Handoff coordination correctness depends only on durable semantic routing facts.
|
|
189
|
+
|
|
190
|
+
### 12. Recovery evidence, compatibility and cancellation
|
|
191
|
+
|
|
192
|
+
Apply [RC-01 through RC-05](../design/durable-semantic-coordination/RECOVERY_CONTRACT_CLARIFICATIONS.md).
|
|
193
|
+
Read failures/unknown commit outcomes must not be treated as Target absence.
|
|
194
|
+
Readback reconciles the same reserved execution and transfer facts; admission
|
|
195
|
+
races use existing atomic admission/CAS, never a replacement Target ID.
|
|
196
|
+
|
|
197
|
+
Before continuation, check main/active/Target identities, current required graph
|
|
198
|
+
connections, declared definition compatibility and the same Persistence instance.
|
|
199
|
+
Absent an existing explicit migration/compatibility contract, definition id/version
|
|
200
|
+
must match. Current wiring never reprojects committed HandoffContext. Proc/code
|
|
201
|
+
hashing and automatic semantic code-compatibility detection are not introduced.
|
|
202
|
+
|
|
203
|
+
Result reads follow the specified run's recorded Target, not an unrelated later
|
|
204
|
+
turn's latest active result. Continuation wiring is not required merely to read
|
|
205
|
+
stored status/canonical results.
|
|
206
|
+
|
|
207
|
+
After transfer commit the Source remains handed_off. Observation loss or cancel
|
|
208
|
+
must not roll active responsibility back to main. An explicit cancellation of the
|
|
209
|
+
current turn is routed to that turn's exact reserved Target under existing Agent
|
|
210
|
+
cancellation rules; it does not cancel unrelated/later executions. Preserve the
|
|
211
|
+
facts needed to reconcile cancellation/admission races and process loss.
|
|
212
|
+
|
|
213
|
+
## Required invariants
|
|
214
|
+
|
|
215
|
+
1. Handoff is an Agent-domain capability.
|
|
216
|
+
2. `main_agent.agent_id` is the durable routing anchor.
|
|
217
|
+
3. Active responsibility survives process loss.
|
|
218
|
+
4. Source `:handed_off` and durable responsibility transfer cannot diverge.
|
|
219
|
+
5. Target semantic work never starts without a recoverable reserved execution ID.
|
|
220
|
+
6. A committed Source Handoff is never blindly replayed.
|
|
221
|
+
7. Recovery reconstructs fresh Runtime objects.
|
|
222
|
+
8. Graph/Policy Ruby objects are supplied by Application code, not persisted.
|
|
223
|
+
9. All durable graph participants share one Persistence domain.
|
|
224
|
+
10. Application callback delivery is not part of Handoff durability.
|
|
225
|
+
|
|
226
|
+
## Non-goals
|
|
227
|
+
|
|
228
|
+
This ADR does not:
|
|
229
|
+
|
|
230
|
+
- make arbitrary external effects exactly once;
|
|
231
|
+
- provide restart-spanning local Handoff callback delivery;
|
|
232
|
+
- add distributed transactions across Persistence domains;
|
|
233
|
+
- add generic coordination/session/thread identity;
|
|
234
|
+
- persist HandoffPolicy/Application code;
|
|
235
|
+
- merge Source and Target Agent state.
|