phronomy 0.22.0 → 0.23.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 +200 -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 +152 -0
- data/docs/architecture/persistence.md +175 -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 +82 -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 +112 -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/README.md +172 -0
- data/docs/features.md +31 -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/persistence-backends.md +88 -38
- data/docs/runtime-and-concurrency.md +227 -33
- data/examples/README.md +13 -0
- data/lib/phronomy/agent/agent_execution.rb +19 -15
- 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 +145 -72
- data/lib/phronomy/agent/base.rb +388 -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/execution_coordinator.rb +1975 -587
- 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 +271 -0
- data/lib/phronomy/agent/recovery_coordinator/installation.rb +427 -0
- data/lib/phronomy/agent/recovery_coordinator/resolution.rb +635 -0
- data/lib/phronomy/agent/recovery_coordinator.rb +211 -0
- data/lib/phronomy/agent/recovery_support.rb +512 -0
- data/lib/phronomy/agent/ruby_llm_materializer.rb +22 -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 +336 -102
- 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.rb +77 -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/coordination_state.rb +18 -0
- data/lib/phronomy/multi_agent/coordinator.rb +154 -0
- data/lib/phronomy/multi_agent/execution_coordinator.rb +116 -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/handoff.rb +24 -45
- data/lib/phronomy/multi_agent/handoff_capability_factory.rb +87 -0
- data/lib/phronomy/multi_agent/handoff_context.rb +95 -0
- data/lib/phronomy/multi_agent/handoff_policy.rb +137 -0
- data/lib/phronomy/multi_agent/handoff_projection.rb +191 -0
- data/lib/phronomy/multi_agent/handoff_request.rb +45 -0
- data/lib/phronomy/multi_agent/orchestrator.rb +12 -15
- data/lib/phronomy/multi_agent/runner.rb +98 -0
- data/lib/phronomy/persistence/durable_codec.rb +646 -0
- data/lib/phronomy/persistence/durable_record.rb +117 -0
- data/lib/phronomy/persistence/in_memory.rb +210 -134
- data/lib/phronomy/persistence/migration/initial_format_migration.rb +226 -0
- data/lib/phronomy/persistence/repository_facades.rb +316 -0
- data/lib/phronomy/persistence.rb +81 -41
- 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 +1 -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/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/multi_agent.rbs +39 -0
- data/sig/phronomy/persistence.rbs +62 -4
- data/sig/phronomy/runtime.rbs +1 -4
- data/sig/phronomy/workflow.rbs +2 -2
- data/sig/phronomy.rbs +10 -0
- metadata +65 -17
- 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
|
@@ -2,68 +2,150 @@
|
|
|
2
2
|
|
|
3
3
|
## Status
|
|
4
4
|
|
|
5
|
-
Accepted
|
|
5
|
+
Accepted and implemented by the stateful Agent / ACS-04 Context Policy refactor.
|
|
6
|
+
This decision supersedes the legacy `build_context` / Assembler authority model and
|
|
7
|
+
the intermediate descriptor/registry-based Context Policy design.
|
|
6
8
|
|
|
7
9
|
## Decision
|
|
8
10
|
|
|
9
|
-
Phronomy records the logical execution facts it observes as an append-only Canonical
|
|
11
|
+
Phronomy records the logical execution facts it observes as an append-only Canonical
|
|
12
|
+
Complete Execution Log. Context selection, pruning, ordering, Tool subset selection,
|
|
13
|
+
and compaction do not rewrite or delete those raw facts. They decide only what is
|
|
14
|
+
materialized into one LLM Call Manifest.
|
|
10
15
|
|
|
11
16
|
Journal and Manifest are separate authorities:
|
|
12
17
|
|
|
13
|
-
- **Journal** is the authoritative record of logical execution facts
|
|
14
|
-
- **Manifest** is the authoritative record of the logical input fixed for one
|
|
15
|
-
- Runtime Projection is derived from the Manifest and must not add semantic content that
|
|
18
|
+
- **Journal** is the authoritative record of logical execution facts observed by Phronomy.
|
|
19
|
+
- **Manifest** is the authoritative record of the logical input fixed for one LLM Call.
|
|
20
|
+
- Runtime Projection is derived from the Manifest and must not add semantic content that
|
|
21
|
+
the Manifest did not select.
|
|
16
22
|
|
|
17
|
-
|
|
23
|
+
`agent_id`, `execution_id`, `llm_call_id`, `tool_call_id`, and Journal `sequence` retain
|
|
24
|
+
their narrow identity/provenance meanings. They are not generic Context-selection
|
|
25
|
+
boundaries. Tool protocol dependencies are represented as indivisible conversation
|
|
26
|
+
groups rather than by grouping all records from one execution.
|
|
18
27
|
|
|
19
|
-
|
|
20
|
-
- `execution_id` identifies one AgentExecution. It is provenance, not a Context-selection atom.
|
|
21
|
-
- `llm_call_id` identifies one runtime Provider LLM Call. It is allocated before transport starts and correlates that call's outcome. It is provenance, not a semantic-compaction boundary.
|
|
22
|
-
- `tool_call_id` links an assistant message's Tool Call with the corresponding Tool execution/message.
|
|
23
|
-
- Journal `sequence` is canonical chronology.
|
|
28
|
+
## Context Policy semantic boundary
|
|
24
29
|
|
|
25
|
-
|
|
30
|
+
One LLM Call is prepared through:
|
|
26
31
|
|
|
27
|
-
|
|
32
|
+
```text
|
|
33
|
+
Context sources
|
|
34
|
+
-> ContextPolicyInput
|
|
35
|
+
-> ContextPolicy
|
|
36
|
+
-> ContextPlan
|
|
37
|
+
-> ContextAssembler validation/canonicalization
|
|
38
|
+
-> LLMInputManifest
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`ContextPolicyInput` exposes four top-level semantic categories:
|
|
28
42
|
|
|
29
|
-
|
|
43
|
+
```text
|
|
44
|
+
instruction
|
|
45
|
+
knowledge
|
|
46
|
+
tools
|
|
47
|
+
conversation
|
|
48
|
+
```
|
|
30
49
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- The Tool-role message actually appended to the LLM conversation is a separate logical fact (`tool_message`).
|
|
35
|
-
- Imported Tool-role messages are journaled directly as `tool_message` records; Phronomy does not invent a separate raw Tool execution result for an execution it did not observe.
|
|
50
|
+
The values are immutable Phronomy-defined typed items. Conversation is exposed as an
|
|
51
|
+
ordered array of indivisible groups. An ordinary message is a singleton group; an
|
|
52
|
+
assistant Tool Call and its corresponding Tool-role message(s) form one atomic group.
|
|
36
53
|
|
|
37
|
-
|
|
54
|
+
`ContextPlan` uses the same four categories. Items omitted from the Plan are omitted from
|
|
55
|
+
that LLM Call. Plan ordering expresses Policy ordering within the Framework-owned
|
|
56
|
+
structural layout. ContextAssembler validates required material, group integrity, Tool
|
|
57
|
+
configuration, and the final token budget before it stores the Manifest.
|
|
38
58
|
|
|
39
|
-
|
|
59
|
+
A custom ContextPolicy is ordinary Ruby strategy code. It may select, omit, order,
|
|
60
|
+
compact, retrieve, or otherwise compute its Plan. Phronomy does not expose a public
|
|
61
|
+
Pipeline/Selector/UnitBuilder composition DSL as the Context Policy SPI.
|
|
40
62
|
|
|
41
|
-
|
|
63
|
+
## Agent binding and lifetime
|
|
42
64
|
|
|
43
|
-
|
|
65
|
+
A ContextPolicy is Application code/runtime wiring, not durable Agent state.
|
|
44
66
|
|
|
45
|
-
|
|
67
|
+
An Application binds a **ContextPolicy instance** on the Agent class:
|
|
46
68
|
|
|
47
|
-
|
|
69
|
+
```ruby
|
|
70
|
+
SEARCH_POLICY = SearchContextPolicy.new(vector_store: VECTOR_STORE)
|
|
48
71
|
|
|
49
|
-
|
|
72
|
+
class ResearchAgent < Phronomy::Agent::Base
|
|
73
|
+
context_policy SEARCH_POLICY
|
|
74
|
+
end
|
|
75
|
+
```
|
|
50
76
|
|
|
51
|
-
|
|
77
|
+
If no Policy is bound, the built-in Default instance is used. There is no Policy override
|
|
78
|
+
on Agent instance creation/loading or on `invoke` / `stream` calls. A Policy instance may
|
|
79
|
+
be shared by multiple Agent classes; concurrency safety of a shared Policy and its runtime
|
|
80
|
+
dependencies is the Application's responsibility.
|
|
52
81
|
|
|
53
|
-
|
|
82
|
+
Phronomy does not persist or reconstruct a Policy instance and defines no
|
|
83
|
+
`ContextPolicyDescriptor`, Policy registry, serialized Policy config, or Policy version
|
|
84
|
+
contract. Recovery hydrates finalized `LLMInputManifest` values directly. Future Context
|
|
85
|
+
preparation after Recovery uses the ContextPolicy supplied by the currently loaded
|
|
86
|
+
Application code.
|
|
54
87
|
|
|
55
|
-
##
|
|
88
|
+
## Policy-generated material
|
|
56
89
|
|
|
57
|
-
|
|
90
|
+
A Policy may create new instruction, knowledge, or conversation items through the small
|
|
91
|
+
protected helper/factory API on `ContextPolicy`. Such material is an ordinary current-call
|
|
92
|
+
Plan item; there is no separate `DerivedContentSpec` collection.
|
|
58
93
|
|
|
59
|
-
|
|
94
|
+
Phronomy assigns current-call identity, estimates tokens, freezes/canonicalizes the value,
|
|
95
|
+
and stores content when needed by the finalized Manifest. The Application owns the
|
|
96
|
+
semantic transformation and any internal source mapping/provenance it requires. Merely
|
|
97
|
+
using a generated item in a Manifest does not promote it to a Journal fact or reusable
|
|
98
|
+
future Context candidate.
|
|
99
|
+
|
|
100
|
+
The ACS-04 Tool category is selection-only: a Policy may choose a subset of the effective
|
|
101
|
+
Agent Tool definitions but may not invent a runtime Tool implementation from schema-only
|
|
102
|
+
data.
|
|
103
|
+
|
|
104
|
+
## Default Context Policy
|
|
105
|
+
|
|
106
|
+
The built-in Default is deterministic and model-free:
|
|
107
|
+
|
|
108
|
+
- retain effective instructions in stable order;
|
|
109
|
+
- retain the effective Agent Tool configuration;
|
|
110
|
+
- retain required/current conversation and choose a contiguous recent optional history;
|
|
111
|
+
- choose Knowledge in stable order, skipping an oversized item and continuing with later
|
|
112
|
+
items that fit;
|
|
113
|
+
- allocate variable remainder approximately 60% to conversation and 40% to Knowledge,
|
|
114
|
+
allowing unused share to be reused by the other category;
|
|
115
|
+
- perform no automatic compaction, embedding search, reranking, or additional LLM Call.
|
|
116
|
+
|
|
117
|
+
If required/fixed Context cannot fit, preparation fails rather than silently deleting it.
|
|
118
|
+
|
|
119
|
+
## Execution / Persistence boundary
|
|
120
|
+
|
|
121
|
+
ContextPolicy executes synchronously on an OffloadPool worker, never on the Runtime
|
|
122
|
+
EventLoop. No Phronomy Persistence transaction spans `ContextPolicy#call`.
|
|
123
|
+
|
|
124
|
+
The required preparation shape is:
|
|
125
|
+
|
|
126
|
+
```text
|
|
127
|
+
capture authoritative local/durable snapshot
|
|
128
|
+
-> build immutable ContextPolicyInput
|
|
129
|
+
-> ContextPolicy#call outside Persistence transaction
|
|
130
|
+
-> revalidate durable base/revision/lineage
|
|
131
|
+
-> short commit transaction
|
|
132
|
+
validate Plan
|
|
133
|
+
canonicalize selected/generated content
|
|
134
|
+
final budget validation
|
|
135
|
+
store LLMInputManifest
|
|
136
|
+
save execution state
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
A stale Policy result is rejected by the final revision/watermark precondition. Phronomy
|
|
140
|
+
does not automatically retry or fall back to another Policy after Policy failure.
|
|
60
141
|
|
|
61
142
|
## Consequences
|
|
62
143
|
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
|
|
66
|
-
-
|
|
67
|
-
-
|
|
68
|
-
|
|
69
|
-
-
|
|
144
|
+
- Agent/Workflow durability is independent of ContextPolicy durability.
|
|
145
|
+
- The Manifest, not the historical Policy implementation, is the Recovery authority for a
|
|
146
|
+
finalized Provider input.
|
|
147
|
+
- Application Context strategies remain ordinary reusable Ruby objects with ordinary DI.
|
|
148
|
+
- Framework-internal protocol grouping/validation may use private helpers, but those
|
|
149
|
+
helpers are not the Application Policy API.
|
|
150
|
+
- Replaced descriptor/registry/request/derived-content and selector-pipeline abstractions
|
|
151
|
+
are removed rather than retained as compatibility aliases.
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
# ADR 014: Unified Persistence for Durable State
|
|
2
2
|
|
|
3
|
-
**Status**: Accepted
|
|
4
|
-
**Date**: 2026-08-14
|
|
3
|
+
**Status**: Accepted
|
|
4
|
+
**Date**: 2026-08-14
|
|
5
5
|
**Supersedes**: ADR-009
|
|
6
|
+
**Partially superseded by**:
|
|
7
|
+
- [ADR-020](020-canonical-workflow-instance-identity.md) for Workflow identity terminology only
|
|
8
|
+
- [ADR-021](021-generic-agent-invocation-identity-removal.md) for `InvocationContext` generic session/correlation semantics and Agent-side generic invocation identity
|
|
9
|
+
- [ADR-023](023-fsm-session-incarnation-identity-and-routing.md) for concrete FSMSession incarnation identity and Agent/Tool Runtime routing
|
|
10
|
+
- [ADR-024](024-event-loop-single-writer-agent-runtime.md) for live Agent Runtime ownership, EventLoop single-writer apply, and `AgentExecutionActivation` removal
|
|
11
|
+
- [ADR-025](025-process-local-agent-ownership-and-runtime-admission.md) for one-live-Agent-per-`agent_id` process ownership and same-process top-level execution admission
|
|
12
|
+
- [ADR-026](026-workflow-runtime-admission-and-durable-terminal-barrier.md) for same-process Workflow admission ownership and durable terminal-barrier ordering
|
|
6
13
|
|
|
7
14
|
---
|
|
8
15
|
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# ADR-016: Semantic Multi-Agent Handoff and Runtime-local Ownership
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted.
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
The previous Multi-Agent Handoff implementation encoded routing through a
|
|
10
|
+
special Tool name and sentinel Tool result. That mechanism could identify a
|
|
11
|
+
Target Agent, but it conflated control-plane routing with ordinary Tool execution
|
|
12
|
+
and did not define sufficient Context-transfer, provenance, ownership, or
|
|
13
|
+
next-turn semantics.
|
|
14
|
+
|
|
15
|
+
Handoff is also semantically different from Agent-as-Tool delegation. Delegation
|
|
16
|
+
returns control to the parent coordinator. Handoff transfers active
|
|
17
|
+
responsibility to another Agent.
|
|
18
|
+
|
|
19
|
+
ADR-012 remains authoritative for the canonical execution log and per-call
|
|
20
|
+
Context Policy/Manifest pipeline. ADR-013 remains authoritative for persistent
|
|
21
|
+
Journal-backed Knowledge. This ADR defines the Agent-boundary transfer semantics
|
|
22
|
+
without replacing either decision.
|
|
23
|
+
|
|
24
|
+
## Decision
|
|
25
|
+
|
|
26
|
+
### 1. Handoff is an explicit Source-to-Target semantic edge
|
|
27
|
+
|
|
28
|
+
`Phronomy::MultiAgent::Handoff` binds concrete live `source_agent` and
|
|
29
|
+
`target_agent` instances plus a `HandoffPolicy` and description.
|
|
30
|
+
|
|
31
|
+
Generated Tool names are private transport encoding. They are not Handoff
|
|
32
|
+
identity, durable routing identity, or public semantic contract.
|
|
33
|
+
|
|
34
|
+
### 2. Handoff is control-plane transfer, not an ordinary Tool effect
|
|
35
|
+
|
|
36
|
+
The LLM-facing capability is intercepted before `ToolInvocation` creation. A
|
|
37
|
+
valid Handoff produces a typed private `HandoffRequest` and a Source execution
|
|
38
|
+
terminal outcome of `:handed_off`.
|
|
39
|
+
|
|
40
|
+
No sentinel Tool result is generated. One Provider outcome may not mix Handoff
|
|
41
|
+
with ordinary Tool Calls or contain multiple Handoff requests.
|
|
42
|
+
|
|
43
|
+
### 3. Handoff Policy owns the Agent-boundary transfer decision
|
|
44
|
+
|
|
45
|
+
The initial transfer categories are `current_request`, `history`, `knowledge`,
|
|
46
|
+
and `tool_exchanges`. Application policy classifies each as `required`,
|
|
47
|
+
`forbidden`, or `selectable`, with an include/exclude default for selectable
|
|
48
|
+
categories.
|
|
49
|
+
|
|
50
|
+
Required and forbidden decisions cannot be overridden by the Source Agent.
|
|
51
|
+
Source selection is bounded to the effective Source Context represented by the
|
|
52
|
+
current Manifest.
|
|
53
|
+
|
|
54
|
+
### 4. Context dependency grouping is preserved across the Handoff boundary
|
|
55
|
+
|
|
56
|
+
Context Policy uses typed `ContextPolicyInput` conversation groups rather than
|
|
57
|
+
the removed `Selection::Unit` SPI. Assistant Tool Calls and their corresponding
|
|
58
|
+
Tool-role messages are one indivisible conversation group. When the selected
|
|
59
|
+
Plan is realized, the Manifest records the Framework conversation-group identity
|
|
60
|
+
and classifies canonical Tool exchanges as `tool_exchanges` for Handoff.
|
|
61
|
+
|
|
62
|
+
`HandoffProjection` groups current-format Manifest segments by that conversation
|
|
63
|
+
group identity. It may still read legacy `selection_unit_id` metadata from a
|
|
64
|
+
finalized pre-ACS-04 Manifest, but new Context assembly does not recreate
|
|
65
|
+
`Selection::Unit`. `Selection::Candidate` and `Selection::Constraint` remain
|
|
66
|
+
internal Context-input normalization details only.
|
|
67
|
+
|
|
68
|
+
Handoff Policy answers what may cross the Agent boundary. Target Context Policy
|
|
69
|
+
remains the final authority for what enters each Target LLM call. Transferred
|
|
70
|
+
Handoff Context is therefore converted to selectable Target Context candidates
|
|
71
|
+
before Target Context Policy runs.
|
|
72
|
+
|
|
73
|
+
### 5. Handoff Context is immutable, reference-only transferred material
|
|
74
|
+
|
|
75
|
+
Selected Source content is materialized into an immutable private
|
|
76
|
+
`HandoffContext`; Target execution does not depend on later dereferencing mutable
|
|
77
|
+
Source state. Source and Target may use different Persistence adapters.
|
|
78
|
+
|
|
79
|
+
Transferred material is not automatically adopted into Target Journal or
|
|
80
|
+
persistent Knowledge. Provenance records the original Agent/record/execution/LLM
|
|
81
|
+
call/Tool call where available, plus a multi-hop transfer path.
|
|
82
|
+
|
|
83
|
+
### 6. Active responsibility is Runtime-local
|
|
84
|
+
|
|
85
|
+
One `main_agent` instance anchors one Multi-Agent coordination lifetime. The
|
|
86
|
+
Runtime/EventLoop is the sole mutation authority for `active_agent` and active
|
|
87
|
+
Handoff Context. The current Target remains active across user turns and across
|
|
88
|
+
Runner-facade recreation while the same main Agent instance and Runtime live.
|
|
89
|
+
|
|
90
|
+
Concurrent turns for the same coordination lifetime are rejected by Runtime
|
|
91
|
+
admission rather than racing active-Agent transitions.
|
|
92
|
+
|
|
93
|
+
This coordination state is not durably rehydrated. Runtime/process reset starts
|
|
94
|
+
again at `main_agent`; historical `execution_handed_off` audit facts do not imply
|
|
95
|
+
restored active-Agent ownership.
|
|
96
|
+
|
|
97
|
+
### 7. Public Runner cutover is a clean break
|
|
98
|
+
|
|
99
|
+
The public coordinator is `Phronomy::MultiAgent::Runner.new(main_agent:,
|
|
100
|
+
handoffs:)`. `Phronomy::Agent::Runner`, `agents:`, `routes:`, and the old Agent-owned
|
|
101
|
+
Handoff Tool registry are removed without compatibility aliases.
|
|
102
|
+
|
|
103
|
+
## Consequences
|
|
104
|
+
|
|
105
|
+
Applications gain explicit Handoff semantics, bounded multi-hop continuation,
|
|
106
|
+
Context transfer with provenance, and predictable next-turn ownership.
|
|
107
|
+
|
|
108
|
+
The framework has a stronger separation between control-plane operations and
|
|
109
|
+
ordinary Tools, but applications migrating from the old Runner must update their
|
|
110
|
+
configuration code. Active-Agent continuation remains intentionally process-local
|
|
111
|
+
until a future decision defines a durable coordination identity and rehydration
|
|
112
|
+
contract.
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# ADR-017: Design Authority and ADR Governance
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted.
|
|
6
|
+
|
|
7
|
+
## Date
|
|
8
|
+
|
|
9
|
+
2026-08-23
|
|
10
|
+
|
|
11
|
+
## Context
|
|
12
|
+
|
|
13
|
+
Phronomy contains multiple artifact classes that answer different questions:
|
|
14
|
+
|
|
15
|
+
- ADRs record architecture intent;
|
|
16
|
+
- source/runtime behavior records implementation reality;
|
|
17
|
+
- public API documentation, `@api` classification, compatibility/contract
|
|
18
|
+
tests and runtime behavior collectively define public/extension contracts;
|
|
19
|
+
- RBS represents an already-established typed contract;
|
|
20
|
+
- ordinary tests provide regression evidence;
|
|
21
|
+
- historical and archived design documents preserve context but may describe
|
|
22
|
+
architecture that is no longer current.
|
|
23
|
+
|
|
24
|
+
Treating those artifacts as one universal precedence list creates two failure
|
|
25
|
+
modes: stale design can be reimplemented because it looks authoritative, or an
|
|
26
|
+
accidental implementation change can silently redefine architecture because
|
|
27
|
+
it is newer.
|
|
28
|
+
|
|
29
|
+
The repository also contains two historical ADR files with numeric prefix
|
|
30
|
+
`011`. Numeric labels alone therefore cannot provide an unambiguous identity
|
|
31
|
+
for every existing decision.
|
|
32
|
+
|
|
33
|
+
## Decision
|
|
34
|
+
|
|
35
|
+
### 1. Authority is question-specific
|
|
36
|
+
|
|
37
|
+
Phronomy does **not** define one universal precedence order for every artifact.
|
|
38
|
+
|
|
39
|
+
- Accepted and non-superseded ADRs are the normative authority for
|
|
40
|
+
architecture decisions.
|
|
41
|
+
- Source code and runtime behavior are the authority for current
|
|
42
|
+
implementation reality. They do not silently amend architecture intent.
|
|
43
|
+
- Public APIs and extension SPIs are composite contracts established by
|
|
44
|
+
runtime behavior, `@api` classification, formal API documentation and
|
|
45
|
+
explicit compatibility/contract tests.
|
|
46
|
+
- RBS represents a contract that already exists. RBS does not create new
|
|
47
|
+
public semantics or make an internal API public.
|
|
48
|
+
- Ordinary implementation tests are regression evidence. A test is an
|
|
49
|
+
architecture/contract authority only when the repository explicitly treats
|
|
50
|
+
it as an architecture guard or compatibility/contract test.
|
|
51
|
+
- Historical, Archived and Superseded artifacts are non-normative for current
|
|
52
|
+
architecture.
|
|
53
|
+
|
|
54
|
+
### 2. Inconsistency is explicit
|
|
55
|
+
|
|
56
|
+
When normative architecture, public contract and implementation reality
|
|
57
|
+
disagree, no artifact wins automatically because it is newer.
|
|
58
|
+
|
|
59
|
+
The discrepancy is an architecture inconsistency. It must be recorded,
|
|
60
|
+
reviewed and resolved explicitly by changing the appropriate architecture
|
|
61
|
+
decision, contract, implementation, or combination of them.
|
|
62
|
+
|
|
63
|
+
The required tracking fields and lifecycle are defined in
|
|
64
|
+
[`docs/decisions/README.md`](README.md).
|
|
65
|
+
|
|
66
|
+
### 3. ADR canonical identity is the filename basename
|
|
67
|
+
|
|
68
|
+
The canonical key for an ADR is its filename basename:
|
|
69
|
+
|
|
70
|
+
```text
|
|
71
|
+
NNN-kebab-case-slug
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The numeric prefix is an ordering/display field. Because the repository
|
|
75
|
+
contains two legacy `011` decisions, a bare numeric label is not a globally
|
|
76
|
+
unique historical key.
|
|
77
|
+
|
|
78
|
+
The two legacy `011` ADRs remain in place and are distinguished by:
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
011-build-context-as-single-llm-input-authority
|
|
82
|
+
011-delegate-transport-policy-to-adapters
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
New or modified normative references to either decision must use the full
|
|
86
|
+
canonical key or an explicit file link.
|
|
87
|
+
|
|
88
|
+
### 4. Existing decision history is not renumbered
|
|
89
|
+
|
|
90
|
+
Existing ADR files are not silently renumbered to repair historical numbering
|
|
91
|
+
defects. Renumbering changes references and obscures the actual decision
|
|
92
|
+
history.
|
|
93
|
+
|
|
94
|
+
`016-semantic-multi-agent-handoff` is preserved under its existing key even
|
|
95
|
+
though it was created before this governance rule was canonicalized.
|
|
96
|
+
|
|
97
|
+
### 5. New ADR numbers are monotonic and unique
|
|
98
|
+
|
|
99
|
+
A new ADR receives:
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
max(existing numeric prefix) + 1
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
at the point the new decision is prepared for merge.
|
|
106
|
+
|
|
107
|
+
New ADRs:
|
|
108
|
+
|
|
109
|
+
- use a three-digit numeric prefix;
|
|
110
|
+
- do not fill gaps;
|
|
111
|
+
- do not reuse any existing prefix;
|
|
112
|
+
- update the decision index in the same change.
|
|
113
|
+
|
|
114
|
+
Concurrent unmerged ADRs that select the same number are resolved by rebasing:
|
|
115
|
+
only the not-yet-historical ADR that merges later is renumbered.
|
|
116
|
+
|
|
117
|
+
This decision follows the rule it establishes: `016` already existed and `017`
|
|
118
|
+
was unused, so this Design Authority decision is ADR-017.
|
|
119
|
+
|
|
120
|
+
### 6. Supersession preserves history
|
|
121
|
+
|
|
122
|
+
A superseding decision is normally a new ADR.
|
|
123
|
+
|
|
124
|
+
The superseded ADR remains in the repository with its historical rationale.
|
|
125
|
+
Its status/supersession relation and the decision index are updated so the
|
|
126
|
+
current authority is unambiguous.
|
|
127
|
+
|
|
128
|
+
A materially different architecture is not retroactively edited into an old
|
|
129
|
+
Accepted ADR merely to make repository text appear internally consistent.
|
|
130
|
+
|
|
131
|
+
### 7. Working review registers are planning evidence
|
|
132
|
+
|
|
133
|
+
Carry-forward (`CF-*`), architecture-inconsistency (`AI-*`), Architecture
|
|
134
|
+
Change Set (ACS), and Compatibility Gate (CG) artifacts used during the
|
|
135
|
+
architecture reconciliation program are not themselves normative ADRs.
|
|
136
|
+
|
|
137
|
+
They identify work and evidence. Their output becomes repository authority
|
|
138
|
+
only through the appropriate Accepted ADR, public contract, source/runtime
|
|
139
|
+
implementation, architecture guard, or current explanatory documentation.
|
|
140
|
+
|
|
141
|
+
Unresolved items that survive a working review must be represented by an
|
|
142
|
+
active repository work item or an explicit deferred dependency; they must not
|
|
143
|
+
disappear merely because a working document is retired.
|
|
144
|
+
|
|
145
|
+
### 8. Explanatory architecture documentation is separate
|
|
146
|
+
|
|
147
|
+
Current explanatory architecture documentation will be organized under a
|
|
148
|
+
dedicated canonical entry as part of the documentation-lifecycle migration.
|
|
149
|
+
|
|
150
|
+
Those documents explain the reconciled architecture. They do not replace the
|
|
151
|
+
ADR system as the authority for architecture decisions.
|
|
152
|
+
|
|
153
|
+
## Repository contract
|
|
154
|
+
|
|
155
|
+
Contributors making architecture-sensitive changes must:
|
|
156
|
+
|
|
157
|
+
1. consult [`docs/decisions/README.md`](README.md);
|
|
158
|
+
2. identify relevant Accepted/non-superseded ADRs by canonical key;
|
|
159
|
+
3. distinguish architecture intent from implementation reality and public
|
|
160
|
+
contract evidence;
|
|
161
|
+
4. record unresolved inconsistencies rather than choosing a winner by recency;
|
|
162
|
+
5. update ADR status/index relationships when accepting, amending or
|
|
163
|
+
superseding a decision;
|
|
164
|
+
6. preserve the public-API composite contract and RBS non-authority rule.
|
|
165
|
+
|
|
166
|
+
`CONTRIBUTING.md` carries the developer-facing form of this contract.
|
|
167
|
+
|
|
168
|
+
## Consequences
|
|
169
|
+
|
|
170
|
+
### Positive
|
|
171
|
+
|
|
172
|
+
- Current architecture intent has an explicit repository authority model.
|
|
173
|
+
- Duplicate historical ADR number `011` no longer makes decision references
|
|
174
|
+
ambiguous.
|
|
175
|
+
- New ADR identifiers cannot silently collide.
|
|
176
|
+
- Source drift does not silently become architecture.
|
|
177
|
+
- Stale ADR text does not silently override implementation reality; conflicts
|
|
178
|
+
are visible and reviewable.
|
|
179
|
+
- RBS and ordinary tests cannot accidentally become architecture-definition
|
|
180
|
+
mechanisms.
|
|
181
|
+
- Historical decision rationale is preserved.
|
|
182
|
+
|
|
183
|
+
### Trade-offs
|
|
184
|
+
|
|
185
|
+
- Architecture-sensitive changes must update an index and sometimes track an
|
|
186
|
+
explicit inconsistency.
|
|
187
|
+
- Existing bare references to the legacy numeric label `ADR-011` can remain
|
|
188
|
+
ambiguous in historical material until that material is otherwise touched.
|
|
189
|
+
- Accepted ADRs with known inconsistencies remain visible as normative intent
|
|
190
|
+
until an explicit successor is accepted; the inconsistency process is
|
|
191
|
+
therefore required rather than optional.
|
|
192
|
+
|
|
193
|
+
## Non-goals
|
|
194
|
+
|
|
195
|
+
This ADR does not:
|
|
196
|
+
|
|
197
|
+
- reorganize `spec/design/` into the final current/archive documentation tree;
|
|
198
|
+
- resolve domain-specific architecture inconsistencies;
|
|
199
|
+
- supersede ADR-010, ADR-012, ADR-013, ADR-014, ADR-015 or ADR-016;
|
|
200
|
+
- change Runtime behavior, public APIs, persistence formats, or durable data.
|