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
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
|
-
| **
|
|
81
|
+
| **Durable Agent Handoff** — `Agent::Handoff` and `Agent::HandoffRunner` persist responsibility, immutable Context and exact Target reservation in one Persistence domain; current graph wiring is required for continuation | 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 |
|
|
@@ -120,3 +140,37 @@ durability is provided through `Phronomy::Persistence#workflow_states`; see the
|
|
|
120
140
|
|
|
121
141
|
For runtime ownership and the distinction between public lifecycle APIs and
|
|
122
142
|
private execution machinery, see [Runtime and concurrency](runtime-and-concurrency.md).
|
|
143
|
+
|
|
144
|
+
### Durable semantic coordination (V2 revision 2)
|
|
145
|
+
|
|
146
|
+
Static `Orchestrator.subagent` Tools invoked inside a parent AgentExecution reserve
|
|
147
|
+
child Agent/execution identities before dispatch and reuse retained outcomes.
|
|
148
|
+
Direct `dispatch_parallel` / `fan_out` calls remain Runtime-only convenience APIs.
|
|
149
|
+
`Orchestrator#resume(execution_id)` continues retained parent coordination.
|
|
150
|
+
|
|
151
|
+
`TeamCoordinator` requires `team_definition id:, version:` and provides
|
|
152
|
+
`create`, `load`, `get`, `resume`, `executions`, `result` and scoped `cancel`.
|
|
153
|
+
Team tasks, worker assignments, exact child identities and aggregate outcomes
|
|
154
|
+
are durable facts. Workers still execute sequentially through the existing Agent
|
|
155
|
+
engine. `schedule` and `aggregate` must be pure and replay-safe; aggregate returns
|
|
156
|
+
canonical JSON data. Default assignment results have string keys and error data
|
|
157
|
+
contains class/message strings. Arbitrary Ruby objects are not durable results.
|
|
158
|
+
|
|
159
|
+
`Persistence#execution_result` / `team_execution_result` read exact results;
|
|
160
|
+
`list_executions` / `list_team_executions` discover retained active and terminal
|
|
161
|
+
runs by known owner ID. Reads do not load Runtime owners or deliver callbacks.
|
|
162
|
+
The cursor is an exclusive lexical execution ID and limit must be positive.
|
|
163
|
+
Retention and request correlation remain Application/backend policy.
|
|
164
|
+
|
|
165
|
+
Guarantee: confirmed outcomes are reused, unfinished work keeps its semantic ID,
|
|
166
|
+
and external outcome uncertainty uses Agent Recovery. For F1/F4 this is
|
|
167
|
+
conditional on a conforming retained Persistence domain and compatible current
|
|
168
|
+
wiring. InMemory provides transaction/CAS semantics but is not durable disk storage.
|
|
169
|
+
Callbacks/streams/Tasks are Runtime observations; there is no restart-spanning
|
|
170
|
+
notification outbox, ACK, synthetic fan-out execution or external exactly-once
|
|
171
|
+
claim. See [the migration](migrations/durable-semantic-coordination-v2.md).
|
|
172
|
+
|
|
173
|
+
`Persistence#handoff_result(source_execution_id)` also resolves the exact Handoff
|
|
174
|
+
turn read-only, including a current absent-but-reserved Target, without graph or
|
|
175
|
+
Agent hydration. Purging an idle main Agent removes its Handoff anchor; pending
|
|
176
|
+
coordination rejects purge rather than discarding its reservation.
|
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
|