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,27 +4,9 @@ require "securerandom"
4
4
 
5
5
  module Phronomy
6
6
  module Agent
7
- # Mutable state for one concrete ToolCall.
8
- #
9
- # The object owns Tool-domain state. FSMSession owns transitions, while
10
- # ToolInvocationSessionBuilder converts Task completion into explicit
11
- # Tool-internal events.
12
- #
13
- # @api private
14
7
  class ToolInvocation
15
- AuthorizationOutcome = Struct.new(
16
- :decision,
17
- :facts,
18
- :reason,
19
- :error,
20
- :cancelled
21
- )
22
-
23
- ExecutionOutcome = Struct.new(
24
- :result,
25
- :error,
26
- :cancelled
27
- )
8
+ AuthorizationOutcome = Struct.new(:decision, :facts, :reason, :error, :cancelled)
9
+ ExecutionOutcome = Struct.new(:result, :error, :cancelled)
28
10
 
29
11
  PREFLIGHT_SETTLED_STATES = %i[
30
12
  authorized awaiting_approval rejected failed cancelled completed
@@ -53,21 +35,14 @@ module Phronomy
53
35
  :origin,
54
36
  :metadata
55
37
 
56
- def self.missing(
57
- parent_agent_invocation_id:,
58
- agent:,
59
- tool_call:,
60
- config: {}
61
- )
38
+ def self.missing(parent_agent_invocation_id:, agent:, tool_call:, config: {})
62
39
  new(
63
40
  parent_agent_invocation_id: parent_agent_invocation_id,
64
41
  agent: agent,
65
42
  tool: nil,
66
43
  tool_call: tool_call,
67
44
  config: config
68
- ).tap do |invocation|
69
- invocation.send(:complete_missing_tool!)
70
- end
45
+ ).tap { |invocation| invocation.send(:complete_missing_tool!) }
71
46
  end
72
47
 
73
48
  def initialize(
@@ -81,37 +56,20 @@ module Phronomy
81
56
  id: SecureRandom.uuid
82
57
  )
83
58
  @id = id.to_s
84
- @parent_agent_invocation_id =
85
- parent_agent_invocation_id.to_s
59
+ @parent_agent_invocation_id = parent_agent_invocation_id.to_s
86
60
  @agent = agent
87
61
  @tool = tool
88
62
  @tool_name = tool_call.name.to_s
89
- @tool_call_id =
90
- tool_call.respond_to?(:id) ? tool_call.id : nil
91
- raw_arguments =
92
- if tool_call.respond_to?(:arguments)
93
- tool_call.arguments || {}
94
- else
95
- {}
96
- end
63
+ @tool_call_id = tool_call.respond_to?(:id) ? tool_call.id : nil
64
+ raw_arguments = tool_call.respond_to?(:arguments) ? (tool_call.arguments || {}) : {}
97
65
  @raw_arguments = immutable_copy(raw_arguments)
98
66
  @config = config
99
67
  @approval_policy = approval_policy
100
- @approval_context =
101
- immutable_copy(approval_context || {})
102
- @origin =
103
- if tool&.respond_to?(:tool_origin)
104
- tool.tool_origin.to_sym
105
- else
106
- :local
107
- end
108
- @metadata =
109
- immutable_copy(
110
- tool&.respond_to?(:approval_metadata) ?
111
- tool.approval_metadata :
112
- {}
113
- )
114
-
68
+ @approval_context = immutable_copy(approval_context || {})
69
+ @origin = tool&.respond_to?(:tool_origin) ? tool.tool_origin.to_sym : :local
70
+ @metadata = immutable_copy(
71
+ tool&.respond_to?(:approval_metadata) ? tool.approval_metadata : {}
72
+ )
115
73
  @arguments = nil
116
74
  @facts = {}.freeze
117
75
  @final_decision = nil
@@ -129,17 +87,11 @@ module Phronomy
129
87
  @phase = phase
130
88
  end
131
89
 
132
- # Applies Tool-internal asynchronous completion events on the EventLoop
133
- # thread. The return value tells FSMSession that the event was consumed by
134
- # the context before its declared transition is evaluated.
135
90
  def handle_fsm_event(event)
136
91
  case event.type
137
92
  when :authorization_completed
138
93
  outcome = event.payload
139
- if outcome.is_a?(Exception)
140
- outcome =
141
- AuthorizationOutcome.new(error: outcome)
142
- end
94
+ outcome = AuthorizationOutcome.new(error: outcome) if outcome.is_a?(Exception)
143
95
  apply_authorization_outcome(outcome)
144
96
  true
145
97
  when :execution_completed
@@ -157,37 +109,14 @@ module Phronomy
157
109
  end
158
110
  end
159
111
 
160
- # Deprecated internal compatibility hook. FSMSession no longer calls
161
- # this method; asynchronous results enter through explicit events.
162
- def apply_fsm_action_result(outcome)
163
- event_type =
164
- case outcome
165
- when AuthorizationOutcome
166
- :authorization_completed
167
- when ExecutionOutcome
168
- :execution_completed
169
- else
170
- return self
171
- end
172
- handle_fsm_event(
173
- Phronomy::Event.new(
174
- type: event_type,
175
- target_id: @id,
176
- payload: outcome
177
- )
178
- )
179
- self
180
- end
181
-
182
112
  def validate!
183
113
  return self if terminal?
184
114
 
185
- validated, schema_error =
186
- if @tool.respond_to?(:validate_and_coerce, true)
187
- @tool.send(:validate_and_coerce, @raw_arguments)
188
- else
189
- [@raw_arguments, nil]
190
- end
115
+ validated, schema_error = if @tool.respond_to?(:validate_and_coerce, true)
116
+ @tool.send(:validate_and_coerce, @raw_arguments)
117
+ else
118
+ [@raw_arguments, nil]
119
+ end
191
120
 
192
121
  if schema_error
193
122
  if @tool.class.respond_to?(:on_schema_error) &&
@@ -218,37 +147,22 @@ module Phronomy
218
147
  size: Phronomy.configuration.authorization_pool_size,
219
148
  queue_size: Phronomy.configuration.authorization_queue_size
220
149
  )
221
- timeout = @config.fetch(
222
- :authorization_timeout,
223
- Phronomy.configuration.authorization_timeout
224
- )
225
- cancellation_token = @config[:cancellation_token]
150
+ timeout = @config.fetch(:authorization_timeout, Phronomy.configuration.authorization_timeout)
226
151
  pending = pool.submit(
227
152
  timeout: timeout,
228
- cancellation_token: cancellation_token,
153
+ cancellation_token: @config[:cancellation_token],
229
154
  on_full: :raise
230
- ) do
231
- evaluate_authorization
232
- end
155
+ ) { evaluate_authorization }
233
156
 
234
- task = Phronomy::Task.deferred(
235
- name: "tool-authorization:#{@tool_name}"
236
- )
157
+ task = Phronomy::Task.deferred(name: "tool-authorization:#{@tool_name}")
237
158
  pending.on_complete do |outcome, error|
238
- resolved =
239
- if error
240
- authorization_failure_outcome(error)
241
- else
242
- outcome
243
- end
159
+ resolved = error ? authorization_failure_outcome(error) : outcome
244
160
  task.backend.unblock(resolved, nil)
245
161
  task.transition!(:completed, value: resolved)
246
162
  end
247
163
  task
248
164
  rescue => error
249
- task = Phronomy::Task.deferred(
250
- name: "tool-authorization:#{@tool_name}"
251
- )
165
+ task = Phronomy::Task.deferred(name: "tool-authorization:#{@tool_name}")
252
166
  outcome = authorization_failure_outcome(error)
253
167
  task.backend.unblock(outcome, nil)
254
168
  task.transition!(:completed, value: outcome)
@@ -256,36 +170,28 @@ module Phronomy
256
170
  end
257
171
 
258
172
  def execution_task(runtime: Phronomy::Runtime.instance)
259
- pending =
260
- Phronomy::Agent::ToolExecutor.call_invocation_async(
261
- tool_invocation: self,
262
- cancellation_token: @config[:cancellation_token],
263
- config: @config,
264
- runtime: runtime
265
- )
266
- task = Phronomy::Task.deferred(
267
- name: "tool-execution:#{@tool_name}"
173
+ pending = Phronomy::Agent::ToolExecutor.call_invocation_async(
174
+ tool_invocation: self,
175
+ cancellation_token: @config[:cancellation_token],
176
+ config: @config,
177
+ runtime: runtime
268
178
  )
179
+ task = Phronomy::Task.deferred(name: "tool-execution:#{@tool_name}")
269
180
  pending.on_complete do |result, error|
270
- outcome =
271
- if error
272
- ExecutionOutcome.new(
273
- error: error,
274
- cancelled: error.is_a?(
275
- Phronomy::CancellationError
276
- )
277
- )
278
- else
279
- ExecutionOutcome.new(result: result)
280
- end
181
+ outcome = if error
182
+ ExecutionOutcome.new(
183
+ error: error,
184
+ cancelled: error.is_a?(Phronomy::CancellationError)
185
+ )
186
+ else
187
+ ExecutionOutcome.new(result: result)
188
+ end
281
189
  task.backend.unblock(outcome, nil)
282
190
  task.transition!(:completed, value: outcome)
283
191
  end
284
192
  task
285
193
  rescue => error
286
- task = Phronomy::Task.deferred(
287
- name: "tool-execution:#{@tool_name}"
288
- )
194
+ task = Phronomy::Task.deferred(name: "tool-execution:#{@tool_name}")
289
195
  outcome = ExecutionOutcome.new(
290
196
  error: error,
291
197
  cancelled: error.is_a?(Phronomy::CancellationError)
@@ -295,10 +201,8 @@ module Phronomy
295
201
  task
296
202
  end
297
203
 
298
- def mark_awaiting_approval!
299
- @status = :awaiting_approval
300
- self
301
- end
204
+ def mark_awaiting_approval! = (@status = :awaiting_approval
205
+ self)
302
206
 
303
207
  def mark_authorized!
304
208
  @approval_consumed = true if @status == :awaiting_approval
@@ -306,83 +210,40 @@ module Phronomy
306
210
  self
307
211
  end
308
212
 
309
- def mark_queued!
310
- @status = :queued
311
- self
312
- end
213
+ def mark_queued! = (@status = :queued
214
+ self)
313
215
 
314
- def mark_running!
315
- @status = :running
316
- self
317
- end
216
+ def mark_running! = (@status = :running
217
+ self)
318
218
 
319
- def mark_rejected!
320
- @final_decision = :reject
321
- @status = :rejected
322
- self
323
- end
219
+ def mark_rejected! = (@final_decision = :reject
220
+ @status = :rejected
221
+ self)
324
222
 
325
- def mark_cancelled!
326
- @status = :cancelled
327
- self
328
- end
223
+ def mark_cancelled! = (@status = :cancelled
224
+ self)
329
225
 
330
- def mark_framework_failed!(error)
331
- @error = error
332
- @status = :failed
333
- self
334
- end
335
-
336
- def validation_passed?
337
- @status == :valid
338
- end
339
-
340
- def validation_completed?
341
- @status == :completed
342
- end
343
-
344
- def failed?
345
- @status == :failed
346
- end
226
+ def mark_framework_failed!(error) = (@error = error
227
+ @status = :failed
228
+ self)
347
229
 
348
- def cancelled?
349
- @status == :cancelled
350
- end
351
-
352
- def rejected?
353
- @status == :rejected
354
- end
355
-
356
- def awaiting_approval?
357
- @status == :awaiting_approval
358
- end
359
-
360
- def authorized?
361
- @status == :authorized
362
- end
363
-
364
- def execution_completed?
365
- @status == :completed
366
- end
367
-
368
- def preflight_settled?
369
- PREFLIGHT_SETTLED_STATES.include?(@status)
370
- end
371
-
372
- def terminal?
373
- TERMINAL_STATES.include?(@status)
374
- end
230
+ def validation_passed? = @status == :valid
231
+ def validation_completed? = @status == :completed
232
+ def failed? = @status == :failed
233
+ def cancelled? = @status == :cancelled
234
+ def rejected? = @status == :rejected
235
+ def awaiting_approval? = @status == :awaiting_approval
236
+ def authorized? = @status == :authorized
237
+ def execution_completed? = @status == :completed
238
+ def preflight_settled? = PREFLIGHT_SETTLED_STATES.include?(@status)
239
+ def terminal? = TERMINAL_STATES.include?(@status)
375
240
 
376
241
  def dispatchable?
377
- return false unless @status == :queued
378
-
379
- @final_decision == :allow || @approval_consumed
242
+ @status == :queued && (@final_decision == :allow || @approval_consumed)
380
243
  end
381
244
 
382
245
  def tool_schema
383
- @tool&.respond_to?(:params_schema) ?
384
- @tool.params_schema :
385
- {}
246
+ @tool&.respond_to?(:params_schema) ? @tool.params_schema : {}
386
247
  end
387
248
 
388
249
  def display_arguments
@@ -390,94 +251,55 @@ module Phronomy
390
251
  end
391
252
 
392
253
  def display_facts
393
- sensitive_values = sensitive_argument_values
394
- redact_value(@facts, sensitive_values)
254
+ redact_value(@facts, sensitive_argument_values)
395
255
  end
396
256
 
397
257
  private
398
258
 
399
259
  def evaluate_authorization
400
- request = build_request(
401
- facts: {},
402
- default_decision: nil
403
- )
260
+ request = build_request(facts: {}, default_decision: nil)
404
261
  facts = evaluate_facts
405
262
  request = request.with(facts: facts)
406
263
  default_decision = evaluate_default_decision(request)
407
- request = request.with(
408
- default_decision: default_decision
409
- )
410
- decision =
411
- if @approval_policy
412
- @approval_policy.call(request)
413
- else
414
- default_decision
415
- end
264
+ request = request.with(default_decision: default_decision)
265
+ decision = @approval_policy ? @approval_policy.call(request) : default_decision
416
266
  decision = decision.to_sym if decision.respond_to?(:to_sym)
417
267
 
418
- unless ApprovalEvaluationRequest::VALID_DECISIONS
419
- .include?(decision)
268
+ unless ApprovalEvaluationRequest::VALID_DECISIONS.include?(decision)
420
269
  raise Phronomy::ConfigurationError,
421
- "tool_approval_policy must return :allow, " \
422
- ":require_approval, or :reject " \
270
+ "tool_approval_policy must return :allow, :require_approval, or :reject " \
423
271
  "(got #{decision.inspect})"
424
272
  end
425
273
 
426
- reason =
427
- if decision == :require_approval
428
- if @origin == :mcp
429
- "MCP Tool execution requires approval"
430
- else
431
- "Tool execution requires approval"
432
- end
433
- end
434
-
435
- AuthorizationOutcome.new(
436
- decision: decision,
437
- facts: facts,
438
- reason: reason
439
- )
274
+ reason = if decision == :require_approval
275
+ (@origin == :mcp) ?
276
+ "MCP Tool execution requires approval" :
277
+ "Tool execution requires approval"
278
+ end
279
+ AuthorizationOutcome.new(decision: decision, facts: facts, reason: reason)
440
280
  end
441
281
 
442
282
  def evaluate_facts
443
- callable =
444
- if @tool.class.respond_to?(:approval_facts)
445
- @tool.class.approval_facts
446
- end
283
+ callable = @tool.class.approval_facts if @tool.class.respond_to?(:approval_facts)
447
284
  return {} unless callable
448
285
 
449
- value = callable.call(
450
- @arguments,
451
- @approval_context
452
- )
286
+ value = callable.call(@arguments, @approval_context)
453
287
  unless value.nil? || value.is_a?(Hash)
454
288
  raise Phronomy::ConfigurationError,
455
- "approval_facts must return a Hash or nil " \
456
- "(got #{value.class})"
289
+ "approval_facts must return a Hash or nil (got #{value.class})"
457
290
  end
458
291
  immutable_copy(value || {})
459
292
  end
460
293
 
461
294
  def evaluate_default_decision(request)
462
- requirement =
463
- if @tool.respond_to?(:requires_approval)
464
- @tool.requires_approval
465
- else
466
- false
467
- end
468
- if requirement.respond_to?(:call)
469
- requirement = requirement.call(request)
470
- end
471
-
295
+ requirement = @tool.respond_to?(:requires_approval) ? @tool.requires_approval : false
296
+ requirement = requirement.call(request) if requirement.respond_to?(:call)
472
297
  case requirement
473
- when true
474
- :require_approval
475
- when false, nil
476
- :allow
298
+ when true then :require_approval
299
+ when false, nil then :allow
477
300
  else
478
301
  raise Phronomy::ConfigurationError,
479
- "requires_approval callable must return true or false " \
480
- "(got #{requirement.inspect})"
302
+ "requires_approval callable must return true or false (got #{requirement.inspect})"
481
303
  end
482
304
  end
483
305
 
@@ -506,15 +328,10 @@ module Phronomy
506
328
  AuthorizationOutcome.new(
507
329
  decision: :require_approval,
508
330
  facts: {},
509
- reason:
510
- "Authorization could not be completed safely: " \
511
- "#{error.message}"
331
+ reason: "Authorization could not be completed safely: #{error.message}"
512
332
  )
513
333
  elsif error.is_a?(Phronomy::CancellationError)
514
- AuthorizationOutcome.new(
515
- error: error,
516
- cancelled: true
517
- )
334
+ AuthorizationOutcome.new(error: error, cancelled: true)
518
335
  else
519
336
  AuthorizationOutcome.new(error: error)
520
337
  end
@@ -522,48 +339,38 @@ module Phronomy
522
339
 
523
340
  def apply_authorization_outcome(outcome)
524
341
  unless outcome.is_a?(AuthorizationOutcome)
525
- raise Phronomy::Error,
526
- "Expected AuthorizationOutcome, got #{outcome.class}"
342
+ raise Phronomy::Error, "Expected AuthorizationOutcome, got #{outcome.class}"
527
343
  end
528
-
529
344
  @facts = immutable_copy(outcome.facts || {})
530
345
  @authorization_reason = outcome.reason
531
346
  @error = outcome.error
532
-
533
347
  if outcome.cancelled
534
348
  @status = :cancelled
535
349
  elsif outcome.error
536
350
  @status = :failed
537
351
  else
538
352
  @final_decision = outcome.decision
539
- @status =
540
- case outcome.decision
541
- when :allow
542
- :authorized
543
- when :require_approval
544
- :awaiting_approval
545
- when :reject
546
- :rejected
547
- end
353
+ @status = case outcome.decision
354
+ when :allow then :authorized
355
+ when :require_approval then :awaiting_approval
356
+ when :reject then :rejected
357
+ end
548
358
  end
549
359
  end
550
360
 
551
361
  def apply_execution_outcome(outcome)
552
362
  unless outcome.is_a?(ExecutionOutcome)
553
- raise Phronomy::Error,
554
- "Expected ExecutionOutcome, got #{outcome.class}"
363
+ raise Phronomy::Error, "Expected ExecutionOutcome, got #{outcome.class}"
555
364
  end
556
-
557
365
  @result = outcome.result
558
366
  @error = outcome.error
559
- @status =
560
- if outcome.cancelled
561
- :cancelled
562
- elsif outcome.error
563
- :failed
564
- else
565
- :completed
566
- end
367
+ @status = if outcome.cancelled
368
+ :cancelled
369
+ elsif outcome.error
370
+ :failed
371
+ else
372
+ :completed
373
+ end
567
374
  end
568
375
 
569
376
  def complete_missing_tool!
@@ -575,8 +382,7 @@ module Phronomy
575
382
  case value
576
383
  when Hash
577
384
  value.each_with_object({}) do |(key, item), result|
578
- result[immutable_copy(key)] =
579
- immutable_copy(item)
385
+ result[immutable_copy(key)] = immutable_copy(item)
580
386
  end.freeze
581
387
  when Array
582
388
  value.map { |item| immutable_copy(item) }.freeze
@@ -589,9 +395,7 @@ module Phronomy
589
395
 
590
396
  def redact_for_display(value)
591
397
  if @tool&.respond_to?(:redacted_args, true)
592
- immutable_copy(
593
- @tool.send(:redacted_args, value || {})
594
- )
398
+ immutable_copy(@tool.send(:redacted_args, value || {}))
595
399
  else
596
400
  immutable_copy(value || {})
597
401
  end
@@ -599,30 +403,20 @@ module Phronomy
599
403
 
600
404
  def sensitive_argument_values
601
405
  return [] unless @tool&.class&.respond_to?(:redact_params)
602
-
603
- normalized =
604
- (@arguments || @raw_arguments || {})
605
- .transform_keys(&:to_sym)
606
- @tool.class.redact_params.filter_map do |name|
607
- normalized[name]
608
- end
406
+ normalized = (@arguments || @raw_arguments || {}).transform_keys(&:to_sym)
407
+ @tool.class.redact_params.filter_map { |name| normalized[name] }
609
408
  end
610
409
 
611
410
  def redact_value(value, sensitive_values)
612
- if sensitive_values.any? { |sensitive| sensitive == value }
613
- return "[REDACTED]"
614
- end
411
+ return "[REDACTED]" if sensitive_values.any? { |sensitive| sensitive == value }
615
412
 
616
413
  case value
617
414
  when Hash
618
415
  value.each_with_object({}) do |(key, item), result|
619
- result[key] =
620
- redact_value(item, sensitive_values)
416
+ result[key] = redact_value(item, sensitive_values)
621
417
  end.freeze
622
418
  when Array
623
- value.map do |item|
624
- redact_value(item, sensitive_values)
625
- end.freeze
419
+ value.map { |item| redact_value(item, sensitive_values) }.freeze
626
420
  when String
627
421
  "[REDACTED]"
628
422
  else
@@ -4,24 +4,20 @@ module Phronomy
4
4
  module Agent
5
5
  StreamEvent = Data.define(:type, :payload)
6
6
 
7
- def self.run_once(definition:, input:, context: nil, **invoke_options)
7
+ def self.run_once(definition:, input:, context: nil, knowledge: [], **invoke_options)
8
8
  persistence = Phronomy::Persistence::InMemory.new
9
- agent = definition.create(context: context, persistence: persistence)
9
+ agent = definition.create(
10
+ context: context,
11
+ knowledge: knowledge,
12
+ persistence: persistence
13
+ )
10
14
  agent.invoke(input, **invoke_options)
11
15
  end
12
16
  end
13
17
  end
14
18
 
15
- require_relative "agent/fsm_runtime_adapter"
16
19
  require_relative "agent/async_event_api"
17
20
 
18
- unless Phronomy::Agent::AgentInvocationSessionBuilder.singleton_class <
19
- Phronomy::Agent::FsmRuntimeAdapter
20
- Phronomy::Agent::AgentInvocationSessionBuilder.singleton_class.prepend(
21
- Phronomy::Agent::FsmRuntimeAdapter
22
- )
23
- end
24
-
25
21
  unless Phronomy::Agent::Base < Phronomy::Agent::AsyncEventApi
26
22
  Phronomy::Agent::Base.prepend(Phronomy::Agent::AsyncEventApi)
27
23
  end