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
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Architecture Decision Records
|
|
2
|
+
|
|
3
|
+
This directory contains Phronomy Architecture Decision Records (ADRs).
|
|
4
|
+
|
|
5
|
+
The repository-wide architecture authority model is defined by
|
|
6
|
+
[`017-design-authority-and-adr-governance`](017-design-authority-and-adr-governance.md).
|
|
7
|
+
This index is the canonical navigation surface for ADR identity, status, and
|
|
8
|
+
supersession relationships.
|
|
9
|
+
|
|
10
|
+
## Canonical ADR identity
|
|
11
|
+
|
|
12
|
+
The **canonical decision key** is the ADR filename basename, for example:
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
012-canonical-execution-log-and-context-policy
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The three-digit numeric prefix is an ordering/display number. It is not, by
|
|
19
|
+
itself, a globally unique historical identifier because this repository
|
|
20
|
+
already contains two legacy ADRs numbered `011`.
|
|
21
|
+
|
|
22
|
+
Therefore:
|
|
23
|
+
|
|
24
|
+
- new or modified normative material SHOULD link to the ADR file or use its
|
|
25
|
+
full canonical decision key;
|
|
26
|
+
- the bare label `ADR-011` is ambiguous and MUST NOT be used to identify one
|
|
27
|
+
of the two legacy `011` decisions;
|
|
28
|
+
- existing historical text is not rewritten merely to normalize old
|
|
29
|
+
references.
|
|
30
|
+
|
|
31
|
+
## Status and authority
|
|
32
|
+
|
|
33
|
+
ADR status has architecture meaning:
|
|
34
|
+
|
|
35
|
+
- **Accepted** — normative architecture intent unless explicitly superseded.
|
|
36
|
+
- **Amended** — normative architecture intent including its recorded
|
|
37
|
+
amendments.
|
|
38
|
+
- **Superseded** — retained as decision history; non-normative for the
|
|
39
|
+
superseded scope.
|
|
40
|
+
- **Proposed** — not normative until accepted.
|
|
41
|
+
|
|
42
|
+
An Accepted ADR can temporarily disagree with implementation reality. That is
|
|
43
|
+
an **architecture inconsistency**, not permission to silently treat whichever
|
|
44
|
+
artifact is newer as authoritative. The inconsistency must be tracked and
|
|
45
|
+
resolved explicitly.
|
|
46
|
+
|
|
47
|
+
## ADR index
|
|
48
|
+
|
|
49
|
+
| Canonical decision key | Status | Normative now? | Supersession / note |
|
|
50
|
+
|---|---|---:|---|
|
|
51
|
+
| [`001-rubyllm-as-provider-layer`](001-rubyllm-as-provider-layer.md) | Superseded | No | Superseded by [`027-llm-adapter-provider-boundary`](027-llm-adapter-provider-boundary.md); historical RubyLLM adoption rationale retained. |
|
|
52
|
+
| [`002-workflow-context-immutability`](002-workflow-context-immutability.md) | Amended | Yes | `WorkflowContext#merge` remains new-instance semantics; direct generated field writers are EventLoop-owned guarded mutation APIs. |
|
|
53
|
+
| [`003-event-loop-singleton`](003-event-loop-singleton.md) | Accepted | Yes | Current until explicitly superseded/refined. |
|
|
54
|
+
| [`004-invoke-timeout-is-not-cancellation`](004-invoke-timeout-is-not-cancellation.md) | Superseded | No | Superseded by [`011-delegate-transport-policy-to-adapters`](011-delegate-transport-policy-to-adapters.md). |
|
|
55
|
+
| [`005-static-knowledge-class-level-cache`](005-static-knowledge-class-level-cache.md) | Superseded | No | Superseded by [`013-journal-backed-knowledge-as-context-candidates`](013-journal-backed-knowledge-as-context-candidates.md). |
|
|
56
|
+
| [`006-no-built-in-guardrails`](006-no-built-in-guardrails.md) | Superseded | No | Superseded by [`019-filter-contract-and-security-boundaries`](019-filter-contract-and-security-boundaries.md); historical minimal-built-in/Guardrail rationale retained. |
|
|
57
|
+
| [`007-mcp-is-beta-stability`](007-mcp-is-beta-stability.md) | Accepted | Yes | Current. |
|
|
58
|
+
| [`008-orchestrator-uses-os-threads`](008-orchestrator-uses-os-threads.md) | Superseded | No | Superseded by [`010-cooperative-first-concurrency`](010-cooperative-first-concurrency.md). |
|
|
59
|
+
| [`009-state-store-abstraction`](009-state-store-abstraction.md) | Superseded | No | Superseded by [`014-unified-persistence-durable-state`](014-unified-persistence-durable-state.md). |
|
|
60
|
+
| [`010-cooperative-first-concurrency`](010-cooperative-first-concurrency.md) | Accepted | Yes | Current until explicitly refined/superseded. |
|
|
61
|
+
| [`011-build-context-as-single-llm-input-authority`](011-build-context-as-single-llm-input-authority.md) | Superseded | No | Superseded by [`012-canonical-execution-log-and-context-policy`](012-canonical-execution-log-and-context-policy.md). |
|
|
62
|
+
| [`011-delegate-transport-policy-to-adapters`](011-delegate-transport-policy-to-adapters.md) | Accepted | Yes | Legacy duplicate numeric prefix; use the full canonical key. |
|
|
63
|
+
| [`012-canonical-execution-log-and-context-policy`](012-canonical-execution-log-and-context-policy.md) | Accepted | Yes | Current Journal / Manifest / Context authority. |
|
|
64
|
+
| [`013-journal-backed-knowledge-as-context-candidates`](013-journal-backed-knowledge-as-context-candidates.md) | Accepted | Yes | Current persistent Knowledge authority. |
|
|
65
|
+
| [`014-unified-persistence-durable-state`](014-unified-persistence-durable-state.md) | Accepted | Yes | Durable-backend and live-owner/no-reload intent remains current; live Agent execution mutation is refined by ADR-024, same-process Agent identity/admission ownership by ADR-025, and same-process Workflow admission/terminal-barrier ordering by ADR-026. Workflow identity terminology is superseded by ADR-020, generic `InvocationContext` / Agent correlation semantics by ADR-021, and concrete FSMSession/Agent-Tool routing identity by ADR-023. |
|
|
66
|
+
| [`015-tool-public-facade-and-rbs-boundary`](015-tool-public-facade-and-rbs-boundary.md) | Accepted | Yes | Current Tool façade / extension-SPI / RBS boundary. |
|
|
67
|
+
| [`016-semantic-multi-agent-handoff`](016-semantic-multi-agent-handoff.md) | Accepted | Yes | Current semantic Handoff intent; runtime/context-transfer reconciliation is implemented and reflected in current architecture documentation. |
|
|
68
|
+
| [`017-design-authority-and-adr-governance`](017-design-authority-and-adr-governance.md) | Accepted | Yes | Repository-wide architecture authority and ADR governance. |
|
|
69
|
+
| [`018-durability-guarantees-and-failure-model`](018-durability-guarantees-and-failure-model.md) | Accepted | Yes | Repository-wide durability/concurrency/external-effect guarantee vocabulary and F0-F4/X0 failure model. |
|
|
70
|
+
| [`019-filter-contract-and-security-boundaries`](019-filter-contract-and-security-boundaries.md) | Accepted | Yes | Current Filter transform/block and bounded PromptInjectionFilter/isolation boundaries; the follow-up review adds no fourth Context Filter call site and places semantic Context trust in Application ContextPolicy. |
|
|
71
|
+
| [`020-canonical-workflow-instance-identity`](020-canonical-workflow-instance-identity.md) | Accepted | Yes | Canonical logical/durable Workflow identity and CG-01 clean-break migration. |
|
|
72
|
+
| [`021-generic-agent-invocation-identity-removal`](021-generic-agent-invocation-identity-removal.md) | Accepted | Yes | Removes generic Agent/InvocationContext identity and canonical Journal `correlation_id`; CG-02 is closed, with targeted legacy durable-key read compatibility and no eager rewrite. |
|
|
73
|
+
| [`022-agent-execution-parent-identity-and-runtime-routing-boundary`](022-agent-execution-parent-identity-and-runtime-routing-boundary.md) | Accepted | Yes | Canonicalizes Agent-owned Tool/approval logical parent as `execution_id`; CG-03a is reconciled, ADR-023 supplies incarnation routing, and ADR-024 supplies EventLoop result/live-state authority. |
|
|
74
|
+
| [`023-fsm-session-incarnation-identity-and-routing`](023-fsm-session-incarnation-identity-and-routing.md) | Accepted | Yes | FSMSession-owned incarnation identity, session-local Agent/Tool/Multi-Agent routing, and stale-target drop remain current. Its transitional Workflow identity-reservation/admission bridge is superseded by ADR-026; Agent result authority is completed by ADR-024. |
|
|
75
|
+
| [`024-event-loop-single-writer-agent-runtime`](024-event-loop-single-writer-agent-runtime.md) | Accepted | Yes | EventLoop is the single writer of Phronomy-managed live Agent execution state; removes Activation/ActivationRegistry and defines operation-specific Offload result application with current FSM + semantic-ID authority. |
|
|
76
|
+
| [`025-process-local-agent-ownership-and-runtime-admission`](025-process-local-agent-ownership-and-runtime-admission.md) | Accepted | Yes | One mutable live Agent owner per `agent_id` per Runtime; EventLoop is the primary same-process top-level admission authority while Persistence admission remains durable defense. |
|
|
77
|
+
| [`026-workflow-runtime-admission-and-durable-terminal-barrier`](026-workflow-runtime-admission-and-durable-terminal-barrier.md) | Accepted | Yes | EventLoop-owned opaque Workflow admission owner, admission-before-hydration ordering, and FSMSession-integrated durable terminal save barrier with fail-closed uncertain outcomes. |
|
|
78
|
+
| [`027-llm-adapter-provider-boundary`](027-llm-adapter-provider-boundary.md) | Accepted | Yes | Phronomy-owned Provider-call extension boundary; RubyLLM is the default adapter/integration while current input materialization remains RubyLLM-specific. |
|
|
79
|
+
|
|
80
|
+
## Legacy duplicate `011`
|
|
81
|
+
|
|
82
|
+
The repository intentionally preserves both legacy files:
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
011-build-context-as-single-llm-input-authority
|
|
86
|
+
011-delegate-transport-policy-to-adapters
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
They are different decisions. The first is superseded by ADR-012; the second
|
|
90
|
+
remains Accepted.
|
|
91
|
+
|
|
92
|
+
They MUST NOT be silently renumbered. Their filenames are stable historical
|
|
93
|
+
decision keys. New decisions may not introduce another duplicate numeric
|
|
94
|
+
prefix.
|
|
95
|
+
|
|
96
|
+
## Allocating a new ADR identifier
|
|
97
|
+
|
|
98
|
+
For a new ADR:
|
|
99
|
+
|
|
100
|
+
1. inspect all `NNN-*.md` files in this directory;
|
|
101
|
+
2. allocate `max(existing numeric prefix) + 1`;
|
|
102
|
+
3. format the number with three decimal digits;
|
|
103
|
+
4. never fill an old gap and never reuse an existing prefix;
|
|
104
|
+
5. if two unmerged changes select the same number, the change merged later
|
|
105
|
+
rebases and renumbers **only its new, not-yet-historical ADR**;
|
|
106
|
+
6. add the ADR to this index in the same change.
|
|
107
|
+
|
|
108
|
+
Existing ADRs are not renumbered merely because a numbering defect is later
|
|
109
|
+
discovered. `016-semantic-multi-agent-handoff` is therefore preserved as-is.
|
|
110
|
+
Under this rule the Design Authority decision is ADR-017.
|
|
111
|
+
|
|
112
|
+
## Superseding or amending a decision
|
|
113
|
+
|
|
114
|
+
Do not rewrite historical rationale into a fictional current history.
|
|
115
|
+
|
|
116
|
+
When superseding an ADR:
|
|
117
|
+
|
|
118
|
+
- add a new decision describing the new architecture;
|
|
119
|
+
- update the old ADR's status/supersession note only as needed;
|
|
120
|
+
- preserve the old rationale;
|
|
121
|
+
- update this index in the same change;
|
|
122
|
+
- use canonical decision keys/links where a numeric label would be ambiguous.
|
|
123
|
+
|
|
124
|
+
Amendments are appropriate only when the original decision remains the same
|
|
125
|
+
decision and the amendment can be understood without erasing the historical
|
|
126
|
+
rationale. Materially different architecture should normally be a new ADR.
|
|
127
|
+
|
|
128
|
+
## Architecture inconsistency process
|
|
129
|
+
|
|
130
|
+
Architecture intent, public contract, and implementation reality are separate
|
|
131
|
+
authority domains. If they disagree, do not resolve the conflict by recency.
|
|
132
|
+
|
|
133
|
+
An unresolved architecture inconsistency must be recorded in the active
|
|
134
|
+
repository work item (normally a GitHub issue or PR) with at least:
|
|
135
|
+
|
|
136
|
+
```text
|
|
137
|
+
Concern
|
|
138
|
+
Normative authority
|
|
139
|
+
Implementation / contract reality
|
|
140
|
+
Conflict
|
|
141
|
+
Resolution dependency
|
|
142
|
+
Status: OPEN | DEFERRED | RESOLVING | RESOLVED
|
|
143
|
+
Final resolution
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Existing Workstream carry-forward (`CF-*`) and architecture-inconsistency
|
|
147
|
+
(`AI-*`) registers are discovery/planning evidence, not normative architecture.
|
|
148
|
+
During the current reconciliation program their unresolved contents are
|
|
149
|
+
consolidated into named Architecture Change Sets (ACS) and Compatibility Gates
|
|
150
|
+
(CG). A repository change that resolves such an item should identify the
|
|
151
|
+
corresponding ACS/CG in its PR/commit rationale.
|
|
152
|
+
|
|
153
|
+
A legacy carry-forward proposition is closed only by one of:
|
|
154
|
+
|
|
155
|
+
- adoption into current source/public contract/current documentation;
|
|
156
|
+
- an Accepted ADR;
|
|
157
|
+
- explicit rejection/non-carry disposition; or
|
|
158
|
+
- an open repository work item when the resolution is intentionally deferred.
|
|
159
|
+
|
|
160
|
+
Working registers must not be copied wholesale into current architecture
|
|
161
|
+
documentation as if they were normative decisions.
|
|
162
|
+
|
|
163
|
+
## Relationship to explanatory architecture documentation
|
|
164
|
+
|
|
165
|
+
ADRs record normative decisions. Explanatory architecture documents describe
|
|
166
|
+
the current reconciled system but do not supersede ADRs merely by being newer.
|
|
167
|
+
|
|
168
|
+
Current explanatory architecture starts at
|
|
169
|
+
[`docs/architecture.md`](../architecture.md). Non-current design snapshots are
|
|
170
|
+
segregated under `docs/archive/design/` and are non-normative. Explanatory
|
|
171
|
+
architecture documents describe the reconciled current system but do not
|
|
172
|
+
supersede ADRs merely by being newer.
|
data/docs/features.md
CHANGED
|
@@ -18,9 +18,9 @@ for production deployments.
|
|
|
18
18
|
| Feature | Stability |
|
|
19
19
|
|---|---|
|
|
20
20
|
| **Workflow** — Stateful, branching workflows with `wait_state` and explicit events | Stable |
|
|
21
|
-
| **Agent** — Stateful ReAct-style agents with stable `agent_id`, persistence-backed execution state, canonical history,
|
|
21
|
+
| **Agent** — Stateful ReAct-style agents with stable `agent_id`, one mutable live owner per Runtime, persistence-backed execution state, canonical history, and conversation context | Stable |
|
|
22
22
|
| **Tool authoring façade** — `Phronomy::Tool::Base` is the public authoring name for the existing Capability base class; the legacy namespace remains compatible | Beta |
|
|
23
|
-
| **Unified Persistence** — One durable backend abstraction for Agent state and Workflow `workflow_states`; live Agent/Workflow state remains owned by
|
|
23
|
+
| **Unified Persistence** — One durable backend abstraction for Agent state and Workflow `workflow_states`; live Agent/Workflow state remains owned by Runtime/session authority between durable commits; custom backends implement the documented Backend SPI and repository/transaction semantics | Beta |
|
|
24
24
|
| **LLMAdapter SPI** — `Phronomy::LLMAdapter::Base#complete` / `#stream` define the Beta call-adapter extension boundary; Phronomy owns async/offload wrapping | Beta |
|
|
25
25
|
| **Before-Large-Language-Model (LLM) Input Hook** — Three-tier per-call LLM input customization via `before_llm_input` and `LLMInputPatch` | Stable |
|
|
26
26
|
| **Context Management** — Journal + Context Policy + per-LLM-call Manifest with token-budget-aware selection and protocol-safe Tool Call / Tool message dependencies | Stable |
|
|
@@ -28,9 +28,18 @@ for production deployments.
|
|
|
28
28
|
| **`PromptInjectionFilter`** — Built-in pattern-based prompt-injection filter | Beta |
|
|
29
29
|
| **Capability redaction/result-size controls** — `redact_params` and `max_result_size` | Beta |
|
|
30
30
|
| **Output Parser** — JSON and Struct-mapped parsers for structured LLM responses | Stable |
|
|
31
|
-
| **Tracing** — Pluggable
|
|
31
|
+
| **Tracing** — Pluggable logical-operation span observability with bounded automatic coverage | Stable |
|
|
32
32
|
| **Error Taxonomy** — Provider errors translated to Phronomy transport/authentication/rate-limit/context errors | Beta |
|
|
33
33
|
|
|
34
|
+
Agent definition lineage is separate from Agent instance identity. A named
|
|
35
|
+
concrete Agent may declare `agent_definition version: N`; when `id:` is omitted,
|
|
36
|
+
its fully-qualified Ruby class name is the stable `agent_definition_id`.
|
|
37
|
+
Applications may keep a lineage independent of Ruby constant naming with an
|
|
38
|
+
explicit `id:`. Concrete subclasses declare their own definition revision
|
|
39
|
+
rather than implicitly inheriting the parent revision. The Stable
|
|
40
|
+
`before_llm_input` context exposes the semantic revision as
|
|
41
|
+
`agent_definition_version`.
|
|
42
|
+
|
|
34
43
|
## Knowledge and integration
|
|
35
44
|
|
|
36
45
|
| Feature | Stability |
|
|
@@ -47,10 +56,12 @@ for production deployments.
|
|
|
47
56
|
|
|
48
57
|
| Feature | Stability |
|
|
49
58
|
|---|---|
|
|
50
|
-
| **EventLoop** — Runtime-owned event-driven execution core shared by Agent, ToolInvocation, Workflow, and MultiAgent sessions | Beta |
|
|
51
|
-
| **
|
|
59
|
+
| **EventLoop** — Runtime-owned event-driven execution core shared by Agent, ToolInvocation, Workflow, and MultiAgent sessions; single writer for Phronomy-managed live Agent execution state | Beta |
|
|
60
|
+
| **Agent execution result authority** — Session-local routing plus current FSM state and purpose-specific semantic IDs (`llm_call_id`, `tool_invocation_id`) reject stale async results | Beta |
|
|
61
|
+
| **Agent live ownership/admission** — Runtime owns one mutable live Agent per `agent_id`; EventLoop rejects competing same-Agent top-level executions while suspension retains the logical execution slot; Persistence admission remains durable defense | Beta |
|
|
62
|
+
| **Workflow durable admission** — Same-process admission is keyed by durable `workflow_instance_id` from pre-hydration admission through the durable terminal/halted save barrier; admission-owner representation is Runtime-internal | Beta |
|
|
52
63
|
| **`invoke` / `invoke_async`** — Blocking and non-blocking Agent/Workflow entry points | Stable |
|
|
53
|
-
| **Agent async events** — `
|
|
64
|
+
| **Agent async events** — one Runtime-only `on_event` listener is bound at Agent `new` / `create` / `load`; invoke/stream operations publish through that listener and streaming additionally emits `:token` | Beta |
|
|
54
65
|
| **`stream` / `stream_async`** — Event callbacks execute on EventLoop and must return quickly | Beta |
|
|
55
66
|
| **`stream_callback_error_policy`** — Terminal event callback error policy (`:report` / `:fail_task`) | Beta |
|
|
56
67
|
| **Task completion contract** — `Task` is the common caller-facing completion handle for EventLoop/FSMSession lifecycles and OffloadPool work | Beta |
|
|
@@ -58,7 +69,7 @@ for production deployments.
|
|
|
58
69
|
| **CancellationToken** — Cooperative cancellation with explicit `cancel!`, lazy monotonic deadlines, and callback registration | Experimental |
|
|
59
70
|
| **Tool `execution_mode`** — `:cooperative` for short EventLoop-safe work; `:offloaded` for synchronous work that must stay off EventLoop | Experimental |
|
|
60
71
|
| **OffloadPool sizing** — `offload_pool_size` / `offload_queue_size`; named pools available for application-owned isolation | Beta |
|
|
61
|
-
| **InvocationContext** — Explicit
|
|
72
|
+
| **InvocationContext** — Explicit cancellation/deadline/policy plus observability-correlation context for Agent and Workflow invocations | Beta |
|
|
62
73
|
| **Metrics** — OffloadPool active/queue/abandoned metrics plus EventLoop queue/lag metrics | Beta |
|
|
63
74
|
| **Runtime lifecycle** — Runtime-owned EventLoop and terminal `Runtime#shutdown` | Beta |
|
|
64
75
|
|
|
@@ -67,13 +78,18 @@ for production deployments.
|
|
|
67
78
|
| Feature | Stability |
|
|
68
79
|
|---|---|
|
|
69
80
|
| **Workflow asynchronous pattern** — Start async work, return immediately, and continue through `Workflow#signal` | Beta |
|
|
70
|
-
| **Multi-
|
|
81
|
+
| **Semantic Multi-Agent Handoff** — `MultiAgent::Handoff` transfers active responsibility from an explicit Source Agent to a Target Agent, projects policy-bounded Context with provenance, keeps the Target active across user turns within the same Runtime/main-Agent lifetime, and does not claim durable continuation across Runtime reset | Beta |
|
|
71
82
|
| **GeneratorVerifier** — Generator-Verifier loop with injectable prompts/parsers | Beta |
|
|
72
83
|
| **`Phronomy::MultiAgent::Orchestrator`** — Parallel subagent dispatch, fan-out, and `subagent` DSL | Beta |
|
|
73
84
|
| **`Phronomy::MultiAgent::TeamCoordinator`** — LLM coordinator with stateful worker Agents | Beta |
|
|
74
85
|
| **SharedState** — Peer-agent shared-state coordination | Experimental |
|
|
75
|
-
| **Human-in-the-loop approval** —
|
|
76
|
-
| **`tool_approval_policy`** — Application-defined allow/approve/reject policy | Beta |
|
|
86
|
+
| **Human-in-the-loop approval** — durable suspension publishes `:approval_required` through the Agent listener; `approve` / `approve_async` resumes the same logical `execution_id` with a fresh FSMSession incarnation | Beta |
|
|
87
|
+
| **`tool_approval_policy`** — Application-defined allow/approve/reject policy using a value-only `ApprovalEvaluationRequest` without live Agent/Tool references | Beta |
|
|
88
|
+
|
|
89
|
+
For Handoff, Application `HandoffPolicy` controls what may cross the Source/Target
|
|
90
|
+
boundary. Transferred Handoff Context is immutable request-scoped material rather
|
|
91
|
+
than automatic Target Journal/Knowledge adoption. Target Context Policy still
|
|
92
|
+
selects what enters each individual Target LLM call.
|
|
77
93
|
|
|
78
94
|
## Public API boundary
|
|
79
95
|
|
|
@@ -100,12 +116,16 @@ extension contracts are deliberate exceptions to the ordinary
|
|
|
100
116
|
application-facing interpretation of `@api public`: they are compatibility
|
|
101
117
|
contracts for implementers. Extension implementations must not depend on Runtime
|
|
102
118
|
private execution objects such as EventLoop/FSMSession/OffloadPool operation
|
|
103
|
-
records.
|
|
119
|
+
records or EventLoop Agent execution entries.
|
|
104
120
|
|
|
105
121
|
`Phronomy::StateStore` is no longer a public backend abstraction. Workflow
|
|
106
122
|
durability is provided through `Phronomy::Persistence#workflow_states`; see the
|
|
107
123
|
0.19 migration guide when upgrading code that used `state_store:`.
|
|
108
124
|
|
|
125
|
+
For current architecture/authority boundaries, start at
|
|
126
|
+
[Architecture](architecture.md). Archived historical design material is not a
|
|
127
|
+
current compatibility contract.
|
|
128
|
+
|
|
109
129
|
## Advanced and internal APIs
|
|
110
130
|
|
|
111
131
|
| Feature | Stability |
|
data/docs/getting-started.md
CHANGED
|
@@ -100,7 +100,7 @@ result = agent.invoke("What is my name?")
|
|
|
100
100
|
puts result[:output]
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
Resolve the same logical Agent when the same Persistence backend is available:
|
|
104
104
|
|
|
105
105
|
```ruby
|
|
106
106
|
agent = ResearchAgent.load(
|
|
@@ -111,8 +111,24 @@ agent = ResearchAgent.load(
|
|
|
111
111
|
agent.invoke("Continue our previous discussion.")
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
A Runtime has at most one mutable live Agent object for an `agent_id`. If the
|
|
115
|
+
Agent is already live, repeated `load` returns that exact Ruby object and does
|
|
116
|
+
not reload Persistence. If it is not live, `load` hydrates the durable Agent
|
|
117
|
+
once. Loading an ID that does not exist durably raises
|
|
118
|
+
`Persistence::NotFoundError`.
|
|
119
|
+
|
|
120
|
+
Use `get` when only a process-local lookup is wanted:
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
live = ResearchAgent.get("research-session-42")
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`get` never reads Persistence and returns `nil` when this Runtime has no live
|
|
127
|
+
owner. `new` and `create` are creation operations, not get-or-load operations;
|
|
128
|
+
they raise `AgentAlreadyExistsError` if the requested identity already exists.
|
|
129
|
+
|
|
130
|
+
While the Agent is live, it owns its current AgentRoot/Journal view and
|
|
131
|
+
Runtime/EventLoop owns process-local identity/admission/execution authority.
|
|
116
132
|
Phronomy persists snapshots at defined durability boundaries but does not reload
|
|
117
133
|
mutable Agent/Execution/Journal state before every LLM or Tool step. A conflicting
|
|
118
134
|
external durable write is surfaced as `Persistence::ConflictError` rather than
|
|
@@ -127,8 +143,10 @@ agent.clear_knowledge!
|
|
|
127
143
|
agent.reset_context!
|
|
128
144
|
```
|
|
129
145
|
|
|
130
|
-
`purge!` is different: it
|
|
131
|
-
history from the configured Persistence backend.
|
|
146
|
+
`purge!` is different: it explicitly destroys the logical Agent and removes its
|
|
147
|
+
persisted execution history from the configured Persistence backend. After a
|
|
148
|
+
successful purge the old Ruby object is permanently unusable; the textual
|
|
149
|
+
`agent_id` may then be used to create a new logical Agent.
|
|
132
150
|
|
|
133
151
|
## Sync and async Agent APIs
|
|
134
152
|
|
|
@@ -142,18 +160,25 @@ task = agent.invoke_async("Hello")
|
|
|
142
160
|
result = task.wait_result
|
|
143
161
|
```
|
|
144
162
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
local to the call:
|
|
163
|
+
Public Agent events belong to the live Agent Runtime incarnation. Register
|
|
164
|
+
the listener when the Agent is materialized, not on each invocation:
|
|
148
165
|
|
|
149
166
|
```ruby
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
167
|
+
agent = ResearchAgent.load(
|
|
168
|
+
"research-session-42",
|
|
169
|
+
persistence: persistence,
|
|
170
|
+
on_event: ->(event) {
|
|
171
|
+
puts event.payload[:output] if event.type == :done
|
|
172
|
+
}
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
task = agent.invoke_async("Hello")
|
|
153
176
|
```
|
|
154
177
|
|
|
155
|
-
|
|
156
|
-
both `on_event:` and a block
|
|
178
|
+
`new`, `create`, and `load` also accept an equivalent listener block.
|
|
179
|
+
Supplying both `on_event:` and a construction block is an error. If `load`
|
|
180
|
+
resolves an already-live same-process Agent, supplying any new listener/block
|
|
181
|
+
is also an error; the existing binding is immutable for that Runtime incarnation.
|
|
157
182
|
|
|
158
183
|
`Phronomy::Task` is the common caller-facing completion handle for asynchronous
|
|
159
184
|
Phronomy work. Logical lifecycle progress is driven by EventLoop/FSMSession;
|
|
@@ -163,45 +188,52 @@ OffloadPool. Both paths expose completion as a `Task`.
|
|
|
163
188
|
`Task#wait_result` is for an external caller. Do not block EventLoop waiting for
|
|
164
189
|
a Task that can only complete through that same EventLoop.
|
|
165
190
|
|
|
166
|
-
Streaming
|
|
191
|
+
Streaming uses the same Agent-incarnation listener:
|
|
167
192
|
|
|
168
193
|
```ruby
|
|
169
|
-
agent.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
194
|
+
agent = ResearchAgent.new(
|
|
195
|
+
on_event: ->(event) {
|
|
196
|
+
puts event.payload if event.type == :token
|
|
197
|
+
}
|
|
198
|
+
)
|
|
173
199
|
|
|
174
|
-
|
|
175
|
-
task = agent.stream_async("Explain
|
|
176
|
-
puts event.payload if event.type == :token
|
|
177
|
-
end
|
|
200
|
+
agent.stream("Explain the design")
|
|
201
|
+
task = agent.stream_async("Explain another design")
|
|
178
202
|
```
|
|
179
203
|
|
|
180
|
-
|
|
204
|
+
`stream` / `stream_async` require an Agent event listener because event
|
|
205
|
+
delivery is their public streaming channel. Agent event callbacks execute on
|
|
206
|
+
EventLoop and therefore should return quickly.
|
|
181
207
|
|
|
182
208
|
## Human-in-the-loop approval
|
|
183
209
|
|
|
184
|
-
A Tool requiring approval
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
At a top-level synchronous boundary:
|
|
210
|
+
A Tool requiring approval suspends the durable logical execution without
|
|
211
|
+
settling the original execution Task. Approval notification is delivered
|
|
212
|
+
through the same Agent listener as `:approval_required`:
|
|
188
213
|
|
|
189
214
|
```ruby
|
|
190
|
-
|
|
215
|
+
agent = nil
|
|
216
|
+
agent = ResearchAgent.new(
|
|
217
|
+
on_event: ->(event) {
|
|
218
|
+
next unless event.type == :approval_required
|
|
219
|
+
|
|
220
|
+
request = event.payload.fetch(:request)
|
|
221
|
+
agent.approve_async(
|
|
222
|
+
request.execution_id,
|
|
223
|
+
approval_request_id: request.id,
|
|
224
|
+
approved: true
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
)
|
|
191
228
|
|
|
192
|
-
|
|
193
|
-
request = result[:approval_request]
|
|
194
|
-
result = agent.approve(
|
|
195
|
-
result[:execution_id],
|
|
196
|
-
approval_request_id: request.id,
|
|
197
|
-
approved: true
|
|
198
|
-
)
|
|
199
|
-
end
|
|
229
|
+
result = agent.invoke("Perform the requested protected action")
|
|
200
230
|
```
|
|
201
231
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
232
|
+
Use `approve_async` inside the EventLoop callback; synchronous `approve` is
|
|
233
|
+
for an external thread that already has the durable approval identifiers.
|
|
234
|
+
Approval resume continues the same live Agent instance and AgentInvocation;
|
|
235
|
+
Runtime resolves that process-local owner through EventLoop rather than through a
|
|
236
|
+
shared Activation object. It is not an Agent reload boundary.
|
|
205
237
|
|
|
206
238
|
## Workflow basics
|
|
207
239
|
|
|
@@ -237,16 +269,16 @@ workflow = Phronomy::Workflow.define(
|
|
|
237
269
|
transition from: :finalize, to: :__finish__
|
|
238
270
|
end
|
|
239
271
|
|
|
240
|
-
state = workflow.invoke({draft: ""}, config: {
|
|
272
|
+
state = workflow.invoke({draft: ""}, config: {workflow_instance_id: "doc-1"})
|
|
241
273
|
final = workflow.send_event(state: state, event: :approve)
|
|
242
274
|
puts final.approved
|
|
243
275
|
```
|
|
244
276
|
|
|
245
|
-
`Persistence#workflow_states` is the durable Workflow repository. `
|
|
277
|
+
`Persistence#workflow_states` is the durable Workflow repository. `workflow_instance_id`
|
|
246
278
|
identifies the durable Workflow state and remains stable across resume. Each
|
|
247
279
|
concrete Runtime execution receives a separate internal `fsm_session_id`; the
|
|
248
280
|
application-level `session_id` remains ordinary caller/tracing metadata. Phronomy
|
|
249
|
-
holds owner-aware admission for `
|
|
281
|
+
holds owner-aware admission for `workflow_instance_id` from durable load through terminal
|
|
250
282
|
save so another local invocation cannot start from a stale snapshot while the
|
|
251
283
|
current owner is still committing.
|
|
252
284
|
|
|
@@ -275,7 +307,7 @@ class AnswerContext
|
|
|
275
307
|
|
|
276
308
|
field :question, type: :replace, default: ""
|
|
277
309
|
field :answer, type: :replace, default: nil
|
|
278
|
-
|
|
310
|
+
# workflow_instance_id is framework-owned metadata; do not declare it as a field.
|
|
279
311
|
end
|
|
280
312
|
|
|
281
313
|
class ResearchAgent < Phronomy::Agent::Base
|
|
@@ -293,13 +325,13 @@ workflow = Phronomy::Workflow.define(AnswerContext) do
|
|
|
293
325
|
state :done
|
|
294
326
|
|
|
295
327
|
entry :asking, ->(ctx) {
|
|
296
|
-
|
|
328
|
+
workflow_instance_id = ctx.workflow_instance_id
|
|
297
329
|
|
|
298
330
|
my_agent.invoke_async(ctx.question) do |event|
|
|
299
331
|
next unless event.type == :done
|
|
300
332
|
|
|
301
333
|
workflow.signal(
|
|
302
|
-
|
|
334
|
+
workflow_instance_id: workflow_instance_id,
|
|
303
335
|
event: :answer_ready,
|
|
304
336
|
payload: {answer: event.payload[:output]}
|
|
305
337
|
)
|
data/docs/migrations/0.19.md
CHANGED
|
@@ -98,8 +98,14 @@ A live Agent instance now remains the logical owner of its mutable state after
|
|
|
98
98
|
hydration. Phronomy no longer re-loads mutable AgentRoot/AgentExecution/Journal
|
|
99
99
|
state at each LLM boundary just to obtain freshness.
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
Current Runtime releases keep one process-local mutable Agent owner per `agent_id`
|
|
102
|
+
in a Runtime-owned registry. Repeated `load` returns that exact owner without
|
|
103
|
+
reloading durable state, and `get(agent_id)` performs only a process-local lookup.
|
|
104
|
+
EventLoop separately owns same-Agent top-level execution admission and live
|
|
105
|
+
execution progression; Persistence admission remains a durable integrity defense.
|
|
106
|
+
|
|
107
|
+
Approval resume continues the same process-local live Agent owner and is performed
|
|
108
|
+
through the Agent instance APIs rather than an Activation object:
|
|
103
109
|
|
|
104
110
|
```ruby
|
|
105
111
|
agent.approve(...)
|
|
@@ -125,11 +131,12 @@ If the concrete Agent class is not known, use:
|
|
|
125
131
|
agent = Phronomy::Agent::Base.live_for_execution(execution_id)
|
|
126
132
|
```
|
|
127
133
|
|
|
128
|
-
`live_for_execution` resolves the Runtime-local
|
|
129
|
-
existing Agent instance. It does not accept `persistence:` and does
|
|
130
|
-
Agent or Execution from Persistence. If
|
|
131
|
-
example after process restart), it raises
|
|
132
|
-
until durable execution rehydration is
|
|
134
|
+
`live_for_execution` resolves the Runtime/EventLoop process-local owner and
|
|
135
|
+
returns its existing Agent instance. It does not accept `persistence:` and does
|
|
136
|
+
not load a new Agent or Execution from Persistence. If that live owner has been
|
|
137
|
+
lost (for example after process restart), it raises
|
|
138
|
+
`ExecutionRehydrationRequiredError` until durable execution rehydration is
|
|
139
|
+
implemented.
|
|
133
140
|
|
|
134
141
|
`execution_id` is not an authorization token. HTTP/API applications must still
|
|
135
142
|
verify that the caller is allowed to act on the resolved Agent and approval
|