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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +8 -9
  3. data/CHANGELOG.md +54 -0
  4. data/CONTRIBUTING.md +28 -16
  5. data/README.md +124 -92
  6. data/benchmark/baseline.json +2 -3
  7. data/benchmark/bench_agent_invoke.rb +4 -4
  8. data/benchmark/bench_context_assembler.rb +134 -34
  9. data/benchmark/bench_regression.rb +1 -1
  10. data/benchmark/bench_tool_schema.rb +2 -35
  11. data/docs/decisions/005-static-knowledge-class-level-cache.md +12 -1
  12. data/docs/decisions/010-cooperative-first-concurrency.md +7 -0
  13. data/docs/decisions/011-build-context-as-single-llm-input-authority.md +2 -2
  14. data/docs/decisions/013-journal-backed-knowledge-as-context-candidates.md +122 -0
  15. data/lib/phronomy/agent/agent_invocation.rb +2 -36
  16. data/lib/phronomy/agent/agent_invocation_session_builder.rb +156 -93
  17. data/lib/phronomy/agent/agent_root.rb +1 -2
  18. data/lib/phronomy/agent/base.rb +135 -314
  19. data/lib/phronomy/agent/context/capability/base.rb +166 -297
  20. data/lib/phronomy/agent/context_assembler.rb +65 -29
  21. data/lib/phronomy/agent/context_parts/unit_builders/dependency_aware_unit_builder.rb +19 -89
  22. data/lib/phronomy/agent/context_plan_validator.rb +0 -33
  23. data/lib/phronomy/agent/execution_coordinator.rb +0 -1
  24. data/lib/phronomy/agent/journal_projection.rb +28 -2
  25. data/lib/phronomy/agent/ruby_llm_materializer.rb +2 -111
  26. data/lib/phronomy/agent/shared_state.rb +46 -138
  27. data/lib/phronomy/agent/token_budget_resolver.rb +5 -4
  28. data/lib/phronomy/agent/tool_invocation.rb +108 -314
  29. data/lib/phronomy/agent.rb +6 -10
  30. data/lib/phronomy/configuration.rb +15 -158
  31. data/lib/phronomy/engine/concurrency/cancellation_token.rb +7 -80
  32. data/lib/phronomy/engine/runtime.rb +15 -230
  33. data/lib/phronomy/engine/task_group.rb +30 -102
  34. data/lib/phronomy/llm_context_window/token_budget.rb +8 -79
  35. data/lib/phronomy/multi_agent/orchestrator.rb +152 -204
  36. data/lib/phronomy/multi_agent/team_coordinator.rb +42 -133
  37. data/lib/phronomy/vector_store/in_memory.rb +2 -2
  38. data/lib/phronomy/version.rb +1 -1
  39. data/lib/phronomy.rb +3 -120
  40. data/scripts/api_snapshot.rb +1 -12
  41. metadata +3 -9
  42. data/lib/phronomy/agent/context/knowledge/base.rb +0 -58
  43. data/lib/phronomy/agent/context/knowledge/entity_knowledge.rb +0 -102
  44. data/lib/phronomy/agent/context/knowledge/static_knowledge.rb +0 -58
  45. data/lib/phronomy/agent/fsm_runtime_adapter.rb +0 -210
  46. data/lib/phronomy/knowledge_source.rb +0 -12
  47. data/lib/phronomy/llm_context_window/assembler.rb +0 -191
  48. data/lib/phronomy/llm_context_window/context_version_cache.rb +0 -52
@@ -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
- "trim_messages_2000": 2896552.0
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, guardrail checks, before_completion hooks, response
7
- # handling) with a fully stubbed LLM. No network calls are made.
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 knowledge) — baseline framework overhead.
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 BenchNullTool
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::Assembler#build
3
+ # Benchmark: Manifest-first Context assembly and Default Context Policy.
4
4
  #
5
- # Tests context assembly performance for varying numbers of messages and
6
- # knowledge chunks. This path is exercised on every agent turn.
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
- BenchAsmMessage = Struct.new(:content)
17
+ module BenchContextAssembler
18
+ module_function
12
19
 
13
- def make_assembler(n_messages:, n_chunks:, with_budget: false)
14
- budget = if with_budget
15
- Phronomy::LlmContextWindow::TokenBudget.new(context_window: 4096, max_output_tokens: 512)
16
- end
17
- asm = Phronomy::LlmContextWindow::Assembler.new(budget: budget)
18
- asm.add_instruction("You are a helpful assistant. Answer the user's question.")
19
- n_chunks.times do |i|
20
- asm.add_knowledge("Fact #{i}: The capital of country #{i} is City #{i}.", type: :entity, trusted: true)
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
- BENCH_ASM_ITERATIONS = 1_000
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
- puts "=== bench_context_assembler ==="
30
- Benchmark.bm(40) do |x|
31
- x.report("build(10 msgs, 0 chunks)") do
32
- BENCH_ASM_ITERATIONS.times { make_assembler(n_messages: 10, n_chunks: 0).build }
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
- x.report("build(100 msgs, 5 chunks)") do
36
- BENCH_ASM_ITERATIONS.times { make_assembler(n_messages: 100, n_chunks: 5).build }
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
- x.report("build(1000 msgs, 10 chunks, no budget)") do
40
- (BENCH_ASM_ITERATIONS / 10).times { make_assembler(n_messages: 1000, n_chunks: 10).build }
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
- x.report("build(1000 msgs, 10 chunks, budgeted)") do
44
- (BENCH_ASM_ITERATIONS / 10).times do
45
- # Assembler raises ContextLengthError when messages exceed the budget;
46
- # callers (e.g. Agent::Base#build_context) are expected to pre-trim via
47
- # trim_to_budget before calling build. The rescue here keeps the benchmark
48
- # measuring build's fast path without triggering the error path.
49
- make_assembler(n_messages: 1000, n_chunks: 10, with_budget: true).build
50
- rescue Phronomy::ContextLengthError
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, messages: [], thread_id: nil, config: {}|
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 and static_knowledge_chunks cache
3
+ # Benchmark: Tool::Base params_schema generation.
4
4
  #
5
- # Tool schema generation happens once per tool class (lazily memoised).
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
- Accepted
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
- Partially Superseded by ADR-012 — 2026-08-08
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 the architectural parts of this ADR that treat the legacy `build_context` / `LlmContextWindow::Assembler` path as the long-term single authority for LLM input.
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 || stream_listener
84
- @mode = (mode || (stream_listener ? :stream : :invoke)).to_sym
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