phronomy 0.16.0 → 0.17.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 +8 -9
- data/CHANGELOG.md +54 -0
- data/CONTRIBUTING.md +28 -16
- data/README.md +124 -92
- data/benchmark/baseline.json +2 -3
- data/benchmark/bench_agent_invoke.rb +4 -4
- data/benchmark/bench_context_assembler.rb +134 -34
- data/benchmark/bench_regression.rb +1 -1
- data/benchmark/bench_tool_schema.rb +2 -35
- data/docs/decisions/005-static-knowledge-class-level-cache.md +12 -1
- data/docs/decisions/010-cooperative-first-concurrency.md +7 -0
- data/docs/decisions/011-build-context-as-single-llm-input-authority.md +2 -2
- data/docs/decisions/013-journal-backed-knowledge-as-context-candidates.md +122 -0
- data/lib/phronomy/agent/agent_invocation.rb +2 -36
- data/lib/phronomy/agent/agent_invocation_session_builder.rb +156 -93
- data/lib/phronomy/agent/agent_root.rb +1 -2
- data/lib/phronomy/agent/base.rb +135 -314
- data/lib/phronomy/agent/context/capability/base.rb +166 -297
- data/lib/phronomy/agent/context_assembler.rb +65 -29
- data/lib/phronomy/agent/context_parts/unit_builders/dependency_aware_unit_builder.rb +19 -89
- data/lib/phronomy/agent/context_plan_validator.rb +0 -33
- data/lib/phronomy/agent/execution_coordinator.rb +0 -1
- data/lib/phronomy/agent/journal_projection.rb +28 -2
- data/lib/phronomy/agent/ruby_llm_materializer.rb +2 -111
- data/lib/phronomy/agent/shared_state.rb +46 -138
- data/lib/phronomy/agent/token_budget_resolver.rb +5 -4
- data/lib/phronomy/agent/tool_invocation.rb +108 -314
- data/lib/phronomy/agent.rb +6 -10
- data/lib/phronomy/configuration.rb +15 -158
- data/lib/phronomy/engine/concurrency/cancellation_token.rb +7 -80
- data/lib/phronomy/engine/runtime.rb +15 -230
- data/lib/phronomy/engine/task_group.rb +30 -102
- data/lib/phronomy/llm_context_window/token_budget.rb +8 -79
- data/lib/phronomy/multi_agent/orchestrator.rb +152 -204
- data/lib/phronomy/multi_agent/team_coordinator.rb +42 -133
- data/lib/phronomy/vector_store/in_memory.rb +2 -2
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy.rb +3 -120
- data/scripts/api_snapshot.rb +1 -12
- metadata +3 -9
- data/lib/phronomy/agent/context/knowledge/base.rb +0 -58
- data/lib/phronomy/agent/context/knowledge/entity_knowledge.rb +0 -102
- data/lib/phronomy/agent/context/knowledge/static_knowledge.rb +0 -58
- data/lib/phronomy/agent/fsm_runtime_adapter.rb +0 -210
- data/lib/phronomy/knowledge_source.rb +0 -12
- data/lib/phronomy/llm_context_window/assembler.rb +0 -191
- data/lib/phronomy/llm_context_window/context_version_cache.rb +0 -52
data/benchmark/baseline.json
CHANGED
|
@@ -4,6 +4,5 @@
|
|
|
4
4
|
"tool_params_schema_definition": 19534379.159046534,
|
|
5
5
|
"dispatch_parallel_10": 886.0,
|
|
6
6
|
"cancellation_token_cancelled": 4335060.97443425,
|
|
7
|
-
"cancellation_token_raise_if_cancelled_noop": 3566903.189098373
|
|
8
|
-
|
|
9
|
-
}
|
|
7
|
+
"cancellation_token_raise_if_cancelled_noop": 3566903.189098373
|
|
8
|
+
}
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
# bench_agent_invoke.rb — Agent#invoke framework overhead benchmark.
|
|
4
4
|
#
|
|
5
5
|
# Measures the per-invoke cost of the Phronomy::Agent::Base framework path
|
|
6
|
-
# (context assembly,
|
|
7
|
-
#
|
|
6
|
+
# (context assembly, filter checks, before_llm_input hooks, response handling)
|
|
7
|
+
# with a fully stubbed LLM. No network calls are made.
|
|
8
8
|
#
|
|
9
9
|
# Scenarios:
|
|
10
|
-
# 1. Minimal agent (no tools, no
|
|
10
|
+
# 1. Minimal agent (no tools, no persistent Knowledge) — baseline framework overhead.
|
|
11
11
|
# 2. Tool-aware agent with a registered stub Tool.
|
|
12
12
|
# 3. Agent#stream setup latency (first-chunk time with stubbed stream).
|
|
13
13
|
|
|
@@ -81,7 +81,7 @@ end
|
|
|
81
81
|
bench_tool_class = Class.new(Phronomy::Agent::Base) do
|
|
82
82
|
agent_definition id: "bench-tool", version: 1
|
|
83
83
|
model "stub-model"
|
|
84
|
-
tools
|
|
84
|
+
tools(BenchNullTool => nil)
|
|
85
85
|
|
|
86
86
|
define_method(:build_chat) { |*| BenchStubChat.new(BENCH_RESP) }
|
|
87
87
|
end
|
|
@@ -1,54 +1,154 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Benchmark: Context
|
|
3
|
+
# Benchmark: Manifest-first Context assembly and Default Context Policy.
|
|
4
4
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# ruby benchmark/bench_context_assembler.rb
|
|
7
|
+
#
|
|
8
|
+
# Measures:
|
|
9
|
+
# 1. ContextPolicies::Default selection cost for growing canonical candidate sets.
|
|
10
|
+
# 2. ContextAssembler#build_initial end-to-end Manifest construction.
|
|
11
|
+
#
|
|
12
|
+
# No provider call is performed.
|
|
7
13
|
|
|
8
14
|
require "benchmark"
|
|
9
15
|
require_relative "../lib/phronomy"
|
|
10
16
|
|
|
11
|
-
|
|
17
|
+
module BenchContextAssembler
|
|
18
|
+
module_function
|
|
12
19
|
|
|
13
|
-
def
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
def candidate(index)
|
|
21
|
+
category, role = if (index % 10).zero?
|
|
22
|
+
[:knowledge, :user]
|
|
23
|
+
elsif index.even?
|
|
24
|
+
[:assistant_message, :assistant]
|
|
25
|
+
else
|
|
26
|
+
[:external_message, :user]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
Phronomy::Agent::ContextCandidate.new(
|
|
30
|
+
candidate_id: "candidate-#{index}",
|
|
31
|
+
source_kind: :journal,
|
|
32
|
+
category: category,
|
|
33
|
+
role: role,
|
|
34
|
+
content_ref: "content-#{index}",
|
|
35
|
+
record_id: "record-#{index}",
|
|
36
|
+
agent_id: "bench-agent",
|
|
37
|
+
execution_id: "execution-#{index / 4}",
|
|
38
|
+
llm_call_id: nil,
|
|
39
|
+
tool_call_id: nil,
|
|
40
|
+
sequence: index,
|
|
41
|
+
requirement: :optional,
|
|
42
|
+
priority: 0,
|
|
43
|
+
metadata: {
|
|
44
|
+
"estimated_tokens" => 8,
|
|
45
|
+
"source_sequence" => index
|
|
46
|
+
}
|
|
47
|
+
)
|
|
21
48
|
end
|
|
22
|
-
msgs = Array.new(n_messages) { BenchAsmMessage.new("This is a conversation message.") }
|
|
23
|
-
asm.add_messages(msgs)
|
|
24
|
-
asm
|
|
25
|
-
end
|
|
26
49
|
|
|
27
|
-
|
|
50
|
+
def parts
|
|
51
|
+
{
|
|
52
|
+
unit_builder:
|
|
53
|
+
Phronomy::Agent::ContextParts::UnitBuilders::DependencyAwareUnitBuilder.new,
|
|
54
|
+
required_context_resolver:
|
|
55
|
+
Phronomy::Agent::ContextParts::Requirements::RequiredContextResolver.new,
|
|
56
|
+
recent_first_selector:
|
|
57
|
+
Phronomy::Agent::ContextParts::Selectors::RecentFirstSelector.new,
|
|
58
|
+
token_budget_packer:
|
|
59
|
+
Phronomy::Agent::ContextParts::Budget::TokenBudgetPacker.new
|
|
60
|
+
}.freeze
|
|
61
|
+
end
|
|
28
62
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
63
|
+
def request(candidate_count)
|
|
64
|
+
candidates = Array.new(candidate_count) { |i| candidate(i) }
|
|
65
|
+
Phronomy::Agent::ContextRequest.new(
|
|
66
|
+
agent_id: "bench-agent",
|
|
67
|
+
execution_id: "bench-execution",
|
|
68
|
+
call_sequence: 2,
|
|
69
|
+
call_mode: :complete,
|
|
70
|
+
candidates: candidates,
|
|
71
|
+
token_budget: Phronomy::LlmContextWindow::TokenBudget.new(
|
|
72
|
+
context_window: [candidate_count * 16, 4_096].max,
|
|
73
|
+
max_output_tokens: 512
|
|
74
|
+
),
|
|
75
|
+
model_config: {},
|
|
76
|
+
previous_manifest: nil,
|
|
77
|
+
required_coverage: [],
|
|
78
|
+
parts: parts,
|
|
79
|
+
metadata: {"mandatory_token_estimate" => 32}
|
|
80
|
+
)
|
|
33
81
|
end
|
|
34
82
|
|
|
35
|
-
|
|
36
|
-
|
|
83
|
+
def assembler_fixture
|
|
84
|
+
persistence = Phronomy::Persistence::InMemory.new
|
|
85
|
+
agent_class = Class.new(Phronomy::Agent::Base) do
|
|
86
|
+
agent_definition id: "bench-manifest-context-assembler", version: 1
|
|
87
|
+
model "local-model"
|
|
88
|
+
context_window 16_384
|
|
89
|
+
max_output_tokens 1_024
|
|
90
|
+
instructions "Benchmark instruction"
|
|
91
|
+
end
|
|
92
|
+
agent = agent_class.new(
|
|
93
|
+
persistence: persistence,
|
|
94
|
+
knowledge: ["Persistent benchmark knowledge"]
|
|
95
|
+
)
|
|
96
|
+
root = agent.agent_root
|
|
97
|
+
input_ref = persistence.contents.put_text("benchmark input")
|
|
98
|
+
input_record = Phronomy::Agent::JournalRecord.new(
|
|
99
|
+
agent_id: agent.agent_id,
|
|
100
|
+
kind: :external_message,
|
|
101
|
+
channel: :external,
|
|
102
|
+
role: :user,
|
|
103
|
+
content_ref: input_ref,
|
|
104
|
+
context_generation: root.transcript_generation,
|
|
105
|
+
context_candidate: true
|
|
106
|
+
)
|
|
107
|
+
execution = Phronomy::Agent::AgentExecution.start(
|
|
108
|
+
agent_root: root,
|
|
109
|
+
input_record: input_record,
|
|
110
|
+
metadata: {
|
|
111
|
+
"current_input_ref" => input_ref,
|
|
112
|
+
"current_input_record_id" => input_record.record_id
|
|
113
|
+
}
|
|
114
|
+
).with(
|
|
115
|
+
execution_revision: 0,
|
|
116
|
+
working_records: [input_record]
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
[
|
|
120
|
+
Phronomy::Agent::ContextAssembler.new(agent: agent, persistence: persistence),
|
|
121
|
+
root,
|
|
122
|
+
execution
|
|
123
|
+
]
|
|
37
124
|
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
puts "Manifest-first Context benchmark"
|
|
128
|
+
puts "Ruby #{RUBY_VERSION} on #{RUBY_PLATFORM}"
|
|
129
|
+
puts "=" * 72
|
|
38
130
|
|
|
39
|
-
|
|
40
|
-
|
|
131
|
+
policy = Phronomy::Agent::ContextPolicies::Default.new
|
|
132
|
+
policy_requests = [10, 100, 1_000].to_h do |count|
|
|
133
|
+
[count, BenchContextAssembler.request(count)]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
Benchmark.bm(46) do |x|
|
|
137
|
+
policy_requests.each do |count, request|
|
|
138
|
+
iterations = (count >= 1_000) ? 200 : 1_000
|
|
139
|
+
x.report("DefaultContextPolicy #{count} candidates x#{iterations}") do
|
|
140
|
+
iterations.times { policy.call(request) }
|
|
141
|
+
end
|
|
41
142
|
end
|
|
42
143
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
# expected — budget exceeded
|
|
144
|
+
assembler, root, execution = BenchContextAssembler.assembler_fixture
|
|
145
|
+
x.report("ContextAssembler#build_initial x500") do
|
|
146
|
+
500.times do
|
|
147
|
+
assembler.build_initial(
|
|
148
|
+
input: "benchmark input",
|
|
149
|
+
agent_root: root,
|
|
150
|
+
execution: execution
|
|
151
|
+
)
|
|
52
152
|
end
|
|
53
153
|
end
|
|
54
154
|
end
|
|
@@ -89,7 +89,7 @@ end
|
|
|
89
89
|
# ---------------------------------------------------------------------------
|
|
90
90
|
stub_agent_class = Class.new(Phronomy::Agent::Base) do
|
|
91
91
|
agent_definition id: "bench-stub", version: 1
|
|
92
|
-
define_method(:invoke) do |_input,
|
|
92
|
+
define_method(:invoke) do |_input, thread_id: nil, config: {}|
|
|
93
93
|
{output: "stub", messages: []}
|
|
94
94
|
end
|
|
95
95
|
define_method(:invoke_async) { |input, **_kw| Phronomy::Runtime.instance.spawn(name: "bench-stub") { invoke(input) } }
|
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Benchmark: Tool::Base params_schema generation
|
|
3
|
+
# Benchmark: Tool::Base params_schema generation.
|
|
4
4
|
#
|
|
5
|
-
# Tool schema generation happens once per tool class
|
|
6
|
-
# static_knowledge_chunks is cached at the class level; cache-hit overhead
|
|
7
|
-
# should be negligible compared to cache-miss (which calls the knowledge source).
|
|
5
|
+
# Tool schema generation happens once per tool class and is lazily memoized.
|
|
8
6
|
|
|
9
7
|
require "benchmark"
|
|
10
8
|
require_relative "../lib/phronomy"
|
|
11
9
|
|
|
12
|
-
# --- Tool schema ---
|
|
13
|
-
|
|
14
10
|
class BenchTool10Params < Phronomy::Agent::Context::Capability::Base
|
|
15
11
|
description "A tool with 10 parameters for benchmarking purposes"
|
|
16
12
|
param :param1, type: :string, desc: "First parameter"
|
|
@@ -29,7 +25,6 @@ class BenchTool10Params < Phronomy::Agent::Context::Capability::Base
|
|
|
29
25
|
end
|
|
30
26
|
end
|
|
31
27
|
|
|
32
|
-
# Warm up memoisation
|
|
33
28
|
BenchTool10Params.params_schema_definition
|
|
34
29
|
|
|
35
30
|
BENCH_TOOL_ITERATIONS = 50_000
|
|
@@ -40,31 +35,3 @@ Benchmark.bm(35) do |x|
|
|
|
40
35
|
BENCH_TOOL_ITERATIONS.times { BenchTool10Params.params_schema_definition }
|
|
41
36
|
end
|
|
42
37
|
end
|
|
43
|
-
|
|
44
|
-
# --- static_knowledge_chunks cache ---
|
|
45
|
-
|
|
46
|
-
class BenchKnowledgeSource < Phronomy::Agent::Context::Knowledge::Base
|
|
47
|
-
def fetch(query: nil)
|
|
48
|
-
[{content: "Cached knowledge fact.", type: :static}]
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def static?
|
|
52
|
-
true
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
class BenchAgentWithKnowledge < Phronomy::Agent::Base
|
|
57
|
-
agent_definition id: "bench-knowledge", version: 1
|
|
58
|
-
model "gpt-4o-mini"
|
|
59
|
-
static_knowledge BenchKnowledgeSource.new
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# Warm up cache
|
|
63
|
-
BenchAgentWithKnowledge.static_knowledge_chunks
|
|
64
|
-
|
|
65
|
-
puts "\n=== bench_static_knowledge_cache ==="
|
|
66
|
-
Benchmark.bm(35) do |x|
|
|
67
|
-
x.report("static_knowledge_chunks (hit)") do
|
|
68
|
-
BENCH_TOOL_ITERATIONS.times { BenchAgentWithKnowledge.static_knowledge_chunks }
|
|
69
|
-
end
|
|
70
|
-
end
|
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## Status
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Superseded by ADR-013.
|
|
6
|
+
|
|
7
|
+
This ADR records the historical decision for the former KnowledgeSource-based
|
|
8
|
+
architecture. `static_knowledge`, `KnowledgeSource`, `StaticKnowledge`,
|
|
9
|
+
`EntityKnowledge` and the class-level Knowledge cache are no longer part of the
|
|
10
|
+
active design.
|
|
6
11
|
|
|
7
12
|
## Context
|
|
8
13
|
|
|
@@ -43,3 +48,9 @@ re-fetched on each invocation because their content depends on runtime state.
|
|
|
43
48
|
non-static knowledge source.
|
|
44
49
|
- In tests, the cache must be cleared between examples. `Phronomy.reset_runtime!`
|
|
45
50
|
handles this.
|
|
51
|
+
|
|
52
|
+
## Supersession
|
|
53
|
+
|
|
54
|
+
ADR-013 replaces the source-object/cache model with Journal-backed persistent
|
|
55
|
+
Knowledge selected through Context Policy. The historical rationale above is
|
|
56
|
+
retained only to explain the superseded architecture.
|
|
@@ -5,6 +5,13 @@
|
|
|
5
5
|
Accepted — updated 2026-05-25 to document current scheduler landscape and
|
|
6
6
|
production-cooperative roadmap (Issues #331, #332, #334).
|
|
7
7
|
|
|
8
|
+
> **Historical implementation note (2026-08-09):** The implementation-detail
|
|
9
|
+
> inventory below is intentionally preserved as decision history. Compatibility
|
|
10
|
+
> APIs subsequently removed from the active contract — including the
|
|
11
|
+
> `runtime_backend :cooperative` alias and direct Runtime singleton replacement —
|
|
12
|
+
> must not be read as current API guidance. The cooperative-first layering
|
|
13
|
+
> principle remains the decision; use README/current source for current symbols.
|
|
14
|
+
|
|
8
15
|
## Context
|
|
9
16
|
|
|
10
17
|
Phronomy provides its own concurrency primitives:
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## Status
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Superseded by ADR-012 — 2026-08-08
|
|
6
6
|
|
|
7
7
|
Originally proposed — 2026-05-31.
|
|
8
8
|
|
|
9
9
|
## Supersession Note
|
|
10
10
|
|
|
11
|
-
ADR-012, **Canonical Complete Execution Log and Context Policy**, supersedes
|
|
11
|
+
ADR-012, **Canonical Complete Execution Log and Context Policy**, supersedes this ADR as an active architecture contract. This document is retained as historical design analysis; references below to `build_context`, `context_overhead`, and `LlmContextWindow::Assembler` are non-normative.
|
|
12
12
|
|
|
13
13
|
In particular, the following parts of this ADR are no longer normative for the stateful Agent architecture:
|
|
14
14
|
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# ADR-013: Journal-backed Knowledge as Context Candidates
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted.
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
The Manifest-first Agent refactor established the Journal as the canonical
|
|
10
|
+
append-only record of Agent state and `LLMInputManifest` as the authority for
|
|
11
|
+
one provider call. The older Knowledge design remained outside that model:
|
|
12
|
+
`StaticKnowledge` and other `KnowledgeSource` objects were fetched separately,
|
|
13
|
+
then concatenated into the mandatory system prompt. That created several
|
|
14
|
+
problems:
|
|
15
|
+
|
|
16
|
+
- static/entity/RAG distinctions described acquisition strategy rather than
|
|
17
|
+
Context semantics;
|
|
18
|
+
- class-level and instance-level Knowledge followed different storage paths;
|
|
19
|
+
- persistent Knowledge did not share Agent persistence/reload semantics;
|
|
20
|
+
- Knowledge bypassed Context Policy and was always mandatory once configured;
|
|
21
|
+
- `static?`, `source`, fetch/caching APIs and EntityKnowledge behavior existed
|
|
22
|
+
primarily to support the obsolete source abstraction.
|
|
23
|
+
|
|
24
|
+
Applications still need two different lifetimes:
|
|
25
|
+
|
|
26
|
+
1. information that becomes durable Agent Knowledge at creation time or later;
|
|
27
|
+
2. request-scoped information used for only one LLM call.
|
|
28
|
+
|
|
29
|
+
## Decision
|
|
30
|
+
|
|
31
|
+
Phronomy has one Knowledge Context category.
|
|
32
|
+
|
|
33
|
+
### Persistent Knowledge
|
|
34
|
+
|
|
35
|
+
Persistent Knowledge is stored as ordinary append-only Journal records with
|
|
36
|
+
`kind: :knowledge`, `channel: :context`, `role: :user` and
|
|
37
|
+
`context_candidate: true`. Content lives in ContentStore and the Journal holds
|
|
38
|
+
its content reference and application metadata.
|
|
39
|
+
|
|
40
|
+
Agent instances accept Knowledge at creation and after creation:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
agent = MyAgent.new(knowledge: ["Policy: ..."])
|
|
44
|
+
agent.add_knowledge("Customer locale: ja-JP")
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`Agent.load` requires no separate Knowledge source reconstruction because the
|
|
48
|
+
records are already persisted with the Agent.
|
|
49
|
+
|
|
50
|
+
### Selection
|
|
51
|
+
|
|
52
|
+
Knowledge is not part of the public conversation transcript.
|
|
53
|
+
`JournalProjection` exposes active Knowledge together with active transcript
|
|
54
|
+
records for Context selection. `ContextCandidateResolver` and Context Policy
|
|
55
|
+
therefore handle persistent Knowledge through the same selection pipeline as
|
|
56
|
+
other optional Context.
|
|
57
|
+
|
|
58
|
+
Knowledge is optional by default. The fact that content is Knowledge does not
|
|
59
|
+
make it mandatory.
|
|
60
|
+
|
|
61
|
+
Selected Knowledge is materialized before ordinary conversation-history
|
|
62
|
+
segments so that persistent background Context is not interleaved into the
|
|
63
|
+
middle of dialogue chronology.
|
|
64
|
+
|
|
65
|
+
### Reset semantics
|
|
66
|
+
|
|
67
|
+
`clear_knowledge!` appends a `knowledge_cleared` marker. Earlier Knowledge
|
|
68
|
+
records remain in the Journal but are excluded from later Context projections.
|
|
69
|
+
No Knowledge-generation counter is required.
|
|
70
|
+
|
|
71
|
+
`clear_transcript!` affects conversation history only. `reset_context!` resets
|
|
72
|
+
both transcript eligibility and Knowledge eligibility while retaining raw
|
|
73
|
+
Journal records.
|
|
74
|
+
|
|
75
|
+
### Per-call Context
|
|
76
|
+
|
|
77
|
+
`before_llm_input` continues to accept `LLMInputPatch#segment_candidates`.
|
|
78
|
+
Those candidates are not persisted. They enter the same Context Policy request
|
|
79
|
+
as Journal-backed candidates and may be omitted when optional and over budget.
|
|
80
|
+
|
|
81
|
+
### Acquisition responsibility
|
|
82
|
+
|
|
83
|
+
Phronomy core does not model `StaticKnowledge`, `EntityKnowledge`,
|
|
84
|
+
`RAGKnowledge` or `KnowledgeSource` subclasses. File loading, retrieval, entity
|
|
85
|
+
extraction and other acquisition strategies belong to applications or Tools.
|
|
86
|
+
Once an application chooses to retain the resulting information, it registers
|
|
87
|
+
plain logical Knowledge with the Agent.
|
|
88
|
+
|
|
89
|
+
## Consequences
|
|
90
|
+
|
|
91
|
+
### Positive
|
|
92
|
+
|
|
93
|
+
- one persistent representation and one Context-selection path;
|
|
94
|
+
- creation-time and post-creation Knowledge behave identically;
|
|
95
|
+
- durable Knowledge naturally survives Agent reload;
|
|
96
|
+
- token-budget selection can omit optional Knowledge without rewriting state;
|
|
97
|
+
- RAG/entity extraction can evolve independently of Agent Context persistence;
|
|
98
|
+
- no class-level static cache or `static?` distinction is needed.
|
|
99
|
+
|
|
100
|
+
### Tradeoffs
|
|
101
|
+
|
|
102
|
+
- applications that previously declared class-level static Knowledge must pass
|
|
103
|
+
common Knowledge when constructing each Agent instance;
|
|
104
|
+
- source-specific refresh behavior is no longer a framework abstraction;
|
|
105
|
+
- provenance that matters to an application must be stored explicitly in
|
|
106
|
+
metadata rather than through a dedicated `source:` API.
|
|
107
|
+
|
|
108
|
+
## Removed contracts
|
|
109
|
+
|
|
110
|
+
This decision removes the active contracts for:
|
|
111
|
+
|
|
112
|
+
- `Phronomy::KnowledgeSource`;
|
|
113
|
+
- `Agent::Context::Knowledge::Base`;
|
|
114
|
+
- `StaticKnowledge`;
|
|
115
|
+
- `EntityKnowledge`;
|
|
116
|
+
- `static_knowledge`, `static_knowledge_sources`, `static_knowledge_chunks`,
|
|
117
|
+
`static_knowledge_refresh!`;
|
|
118
|
+
- `add_knowledge_source`, `instance_knowledge_chunks`;
|
|
119
|
+
- `clear_memory!` / `memory_generation` as obsolete Agent Context concepts.
|
|
120
|
+
|
|
121
|
+
ADR-005 is superseded by this decision. ADR-012 remains the authority for the
|
|
122
|
+
Journal/Manifest separation and Context Policy model.
|
|
@@ -29,7 +29,6 @@ module Phronomy
|
|
|
29
29
|
CALLBACK_FAILED_EVENTS = %i[application_callback_failed].freeze
|
|
30
30
|
|
|
31
31
|
attr_accessor :input,
|
|
32
|
-
:messages,
|
|
33
32
|
:chat,
|
|
34
33
|
:output,
|
|
35
34
|
:usage,
|
|
@@ -59,18 +58,15 @@ module Phronomy
|
|
|
59
58
|
def initialize(
|
|
60
59
|
agent:,
|
|
61
60
|
input:,
|
|
62
|
-
messages:,
|
|
63
61
|
config:,
|
|
64
62
|
approval_policy: nil,
|
|
65
63
|
approval_listener: nil,
|
|
66
64
|
event_listener: nil,
|
|
67
|
-
stream_listener: nil,
|
|
68
65
|
mode: nil,
|
|
69
66
|
id: nil
|
|
70
67
|
)
|
|
71
68
|
@agent = agent
|
|
72
69
|
@input = input
|
|
73
|
-
@messages = Array(messages)
|
|
74
70
|
@config = config
|
|
75
71
|
@thread_id = config[:thread_id]
|
|
76
72
|
@id = (id || config[:agent_invocation_id] || SecureRandom.uuid).to_s
|
|
@@ -80,8 +76,8 @@ module Phronomy
|
|
|
80
76
|
end
|
|
81
77
|
@approval_policy = invocation_policy || approval_policy
|
|
82
78
|
@approval_listener = approval_listener
|
|
83
|
-
@event_listener = event_listener
|
|
84
|
-
@mode = (mode ||
|
|
79
|
+
@event_listener = event_listener
|
|
80
|
+
@mode = (mode || :invoke).to_sym
|
|
85
81
|
|
|
86
82
|
@chat = nil
|
|
87
83
|
@output = nil
|
|
@@ -104,14 +100,6 @@ module Phronomy
|
|
|
104
100
|
@tool_batch_llm_call_id = nil
|
|
105
101
|
end
|
|
106
102
|
|
|
107
|
-
def stream_listener
|
|
108
|
-
@event_listener
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def stream_listener=(listener)
|
|
112
|
-
@event_listener = listener
|
|
113
|
-
end
|
|
114
|
-
|
|
115
103
|
def streaming?
|
|
116
104
|
@mode == :stream
|
|
117
105
|
end
|
|
@@ -180,29 +168,9 @@ module Phronomy
|
|
|
180
168
|
true
|
|
181
169
|
end
|
|
182
170
|
|
|
183
|
-
def apply_fsm_action_result(result)
|
|
184
|
-
event_type =
|
|
185
|
-
if result.respond_to?(:error) &&
|
|
186
|
-
result.error &&
|
|
187
|
-
!result.error.is_a?(ToolCallIntercepted)
|
|
188
|
-
:llm_failed
|
|
189
|
-
else
|
|
190
|
-
:llm_completed
|
|
191
|
-
end
|
|
192
|
-
handle_fsm_event(
|
|
193
|
-
Phronomy::Event.new(
|
|
194
|
-
type: event_type,
|
|
195
|
-
target_id: @id,
|
|
196
|
-
payload: result
|
|
197
|
-
)
|
|
198
|
-
)
|
|
199
|
-
self
|
|
200
|
-
end
|
|
201
|
-
|
|
202
171
|
def accept_tool_calls!(tool_calls, llm_call_id: nil)
|
|
203
172
|
@user_message_sent = true
|
|
204
173
|
@pending_tool_calls = Array(tool_calls)
|
|
205
|
-
@messages = @chat.messages
|
|
206
174
|
@tool_batch_llm_call_id = (llm_call_id || @current_llm_call_id)&.to_s
|
|
207
175
|
@current_llm_call_id = nil
|
|
208
176
|
@pending_tool_calls.each do |tool_call|
|
|
@@ -227,7 +195,6 @@ module Phronomy
|
|
|
227
195
|
@user_message_sent = true
|
|
228
196
|
@output = response.content
|
|
229
197
|
@usage = Phronomy::TokenUsage.from_tokens(response.tokens)
|
|
230
|
-
@messages = @chat.messages
|
|
231
198
|
@pending_tool_calls = []
|
|
232
199
|
@current_llm_call_id = nil
|
|
233
200
|
self
|
|
@@ -298,7 +265,6 @@ module Phronomy
|
|
|
298
265
|
)
|
|
299
266
|
)
|
|
300
267
|
end
|
|
301
|
-
@messages = @chat.messages
|
|
302
268
|
clear_tool_batch!
|
|
303
269
|
self
|
|
304
270
|
end
|