phronomy 0.15.0 → 0.15.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b0ea2e317999f3202e06d5afac0b46509fa22a813531919447040d1e048a087f
4
- data.tar.gz: ae5740df9899b7463b35b54b885948ca1e0e42e01b8b85b1a93fd6437022c90b
3
+ metadata.gz: 98b90b892fcd752aa84d1a00d033bb627df70d143145502f044e5288c7b124a4
4
+ data.tar.gz: c1e37fa68d782d5f506a83e019594e2f77efeb7d37e8f46c8b57286d8b8c9eaa
5
5
  SHA512:
6
- metadata.gz: 123b0998a7a299f97ce981a3561c68990c41e05decb534b4c4664e441abd6cf368e04ecf6cea61df6a1ea2c4016850b093e0c208aa034fc02b1fa498e0078263
7
- data.tar.gz: f1ba3ee6163b1a746bd99f00241daf286ac5eb3d8d1e30f7caa739de1de5dd900f3b19b90a063c4a9f19f494a1e9ac1dca59b4d32ade41deabe71f849803ac62
6
+ metadata.gz: e8a62f9eb2d0971a0faa91d822a9ecff39b495242514dd5144f442c8bcfba85edd84a3e22738df17447604f8c6201169df1f0b308035ad8a5e50603ad379da5b
7
+ data.tar.gz: bcb09d22cf850b447e49e13d022cf760c6b4628fe3df6ea744f91738e1085fea6218bee4ce96c4caf0b6f8a8c07f2900bc7f243ed374cc71c940869f64856ea1
data/CHANGELOG.md CHANGED
@@ -9,6 +9,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ### Changed
13
+
14
+ - Refactor: `Agent::AsyncEventApi` is now the single implementation of
15
+ `invoke`, `invoke_async`, `stream`, `stream_async`, and their session
16
+ lifecycle helpers (`_start_invocation`, `_handle_agent_completion`,
17
+ `_register_tool_invocation_session`, `_start_approval_resume`). The
18
+ duplicate definitions in `Agent::Base` have been removed. No public
19
+ behavior change is intended.
20
+ - Remove `faraday` and `event_stream_parser` from gemspec declared
21
+ dependencies; both are transitive dependencies of `ruby_llm` and are
22
+ not used directly by phronomy.
23
+
12
24
  ### Added
13
25
 
14
26
  - `Workflow#signal(thread_id:, event:, payload:)` for FIFO delivery to a live
@@ -10,6 +10,22 @@ module Phronomy
10
10
  #
11
11
  # @api private
12
12
  module AsyncEventApi
13
+ # Invokes the agent synchronously and returns the terminal result.
14
+ #
15
+ # Provider errors are translated after the configured LLM adapter returns
16
+ # its final result. Phronomy does not replay the Agent invocation.
17
+ #
18
+ # @param input [String, Hash] user input for this invocation
19
+ # @param messages [Array<RubyLLM::Message>] conversation history
20
+ # @param thread_id [String, nil] conversation thread identifier
21
+ # @param config [Hash] additional runtime options
22
+ # @param invocation_context [Phronomy::InvocationContext, nil]
23
+ # first-class invocation context
24
+ # @param on_event [Proc, nil] listener for lifecycle and Tool events
25
+ # @return [Hash] terminal invocation result
26
+ # @raise [Phronomy::SchedulerReentrancyError] when called from the
27
+ # EventLoop thread or a guarded scheduler context
28
+ # @api public
13
29
  def invoke(
14
30
  input,
15
31
  messages: [],
@@ -43,6 +59,23 @@ module Phronomy
43
59
  end
44
60
  end
45
61
 
62
+ # Invokes the agent asynchronously.
63
+ #
64
+ # The returned Task settles after the terminal event listener returns.
65
+ # Lifecycle and Tool events are delivered from the Runtime-owned
66
+ # EventLoop thread.
67
+ #
68
+ # @param input [String, Hash] user input for this invocation
69
+ # @param messages [Array<RubyLLM::Message>] conversation history
70
+ # @param thread_id [String, nil] conversation thread identifier
71
+ # @param config [Hash] additional runtime options
72
+ # @param invocation_context [Phronomy::InvocationContext, nil]
73
+ # first-class invocation context
74
+ # @param on_tool_approval_required [Proc, nil] per-invocation approval
75
+ # notification listener
76
+ # @param on_event [Proc, nil] listener for lifecycle and Tool events
77
+ # @return [Phronomy::Task] Task resolving to the terminal result
78
+ # @api public
46
79
  def invoke_async(
47
80
  input,
48
81
  messages: [],
@@ -80,6 +113,26 @@ module Phronomy
80
113
  result_task
81
114
  end
82
115
 
116
+ # Invokes the agent asynchronously and emits streaming events.
117
+ #
118
+ # Accepts either +on_event:+ or a block. Supplying both is rejected.
119
+ # Token events are emitted only by streaming invocations; lifecycle and
120
+ # Tool events use the same contract as {#invoke_async}.
121
+ #
122
+ # @param input [String, Hash] user input for this invocation
123
+ # @param messages [Array<RubyLLM::Message>] conversation history
124
+ # @param thread_id [String, nil] conversation thread identifier
125
+ # @param config [Hash] additional runtime options
126
+ # @param invocation_context [Phronomy::InvocationContext, nil]
127
+ # first-class invocation context
128
+ # @param on_tool_approval_required [Proc, nil] per-invocation approval
129
+ # notification listener
130
+ # @param on_event [Proc, nil] event listener
131
+ # @yieldparam event [Phronomy::Agent::StreamEvent]
132
+ # @return [Phronomy::Task] Task resolving to the terminal result
133
+ # @raise [ArgumentError] when no listener is supplied or both listener
134
+ # forms are supplied
135
+ # @api public
83
136
  def stream_async(
84
137
  input,
85
138
  messages: [],
@@ -125,6 +178,28 @@ module Phronomy
125
178
  result_task
126
179
  end
127
180
 
181
+ # Invokes the agent synchronously and emits streaming events.
182
+ #
183
+ # Accepts either +on_event:+ or a block. Event callbacks run on the
184
+ # Runtime-owned EventLoop thread; the calling thread waits for the final
185
+ # Task result.
186
+ #
187
+ # @param input [String, Hash] user input for this invocation
188
+ # @param messages [Array<RubyLLM::Message>] conversation history
189
+ # @param thread_id [String, nil] conversation thread identifier
190
+ # @param config [Hash] additional runtime options
191
+ # @param invocation_context [Phronomy::InvocationContext, nil]
192
+ # first-class invocation context
193
+ # @param on_tool_approval_required [Proc, nil] per-invocation approval
194
+ # notification listener
195
+ # @param on_event [Proc, nil] event listener
196
+ # @yieldparam event [Phronomy::Agent::StreamEvent]
197
+ # @return [Hash] terminal invocation result
198
+ # @raise [ArgumentError] when no listener is supplied or both listener
199
+ # forms are supplied
200
+ # @raise [Phronomy::SchedulerReentrancyError] when called from the
201
+ # EventLoop thread or a guarded scheduler context
202
+ # @api public
128
203
  def stream(
129
204
  input,
130
205
  messages: [],
@@ -182,6 +257,7 @@ module Phronomy
182
257
  # Cancellation is checked by the first Agent entry action on the EventLoop
183
258
  # thread so even pre-cancelled invocations produce a terminal event before
184
259
  # the returned Task settles.
260
+ # @api private
185
261
  def _start_invocation(
186
262
  result_task,
187
263
  input,
@@ -396,6 +472,12 @@ module Phronomy
396
472
  event_loop.register(session, completion: completion)
397
473
  end
398
474
 
475
+ # Resumes a suspended AgentInvocation and its pending Tool sessions.
476
+ #
477
+ # Parent completion handling is installed before EventLoop registration,
478
+ # and the parent session is registered before child sessions so immediate
479
+ # child events cannot be lost.
480
+ # @api private
399
481
  def _start_approval_resume(
400
482
  result_task,
401
483
  invocation,
@@ -386,166 +386,6 @@ module Phronomy
386
386
  self
387
387
  end
388
388
 
389
- # Invokes the agent with the given input and returns a result Hash.
390
- # Provider errors are translated after the configured LLM adapter returns
391
- # its final result. Phronomy does not replay the Agent invocation.
392
- #
393
- # @param input [String, Hash] the user message; a Hash may supply
394
- # +:message+, +:query+, or +:user+ as the text key, plus any template
395
- # variables consumed by the configured instructions template.
396
- # @param messages [Array<RubyLLM::Message>] conversation history from a
397
- # previous invocation. The application owns and persists this array;
398
- # pass it on every turn to maintain multi-turn context.
399
- # @param thread_id [String, nil] conversation thread identifier, forwarded
400
- # to the compaction context when on_compact is configured.
401
- # @param config [Hash] additional runtime options:
402
- # +:user_id+ (+String+, optional) — caller identity forwarded to the tracer
403
- # +:session_id+ (+String+, optional) — session identity forwarded to the tracer
404
- # @param invocation_context [Phronomy::InvocationContext, nil] optional first-class context
405
- # object. When present, +thread_id+, +cancellation_token+, and +deadline+ are
406
- # derived from it (existing +config:+ keys take precedence as backward-compat
407
- # aliases). The object is also stored in +config[:invocation_context]+ so that
408
- # +task_id+ / +parent_task_id+ appear in trace spans automatically.
409
- # @return [Hash] +{ output: String, messages: Array, usage: Phronomy::TokenUsage }+,
410
- # or +{ output: nil, suspended: true, checkpoint: Phronomy::Agent::Checkpoint,
411
- # messages: Array }+ when the invocation was suspended awaiting tool approval.
412
- # @raise [Phronomy::FilterBlockError] when an input or output filter rejects the value
413
- # @example Normal invocation
414
- # result = MyAgent.new.invoke("What is Ruby?")
415
- # puts result[:output]
416
- # @example Multi-turn conversation
417
- # result1 = agent.invoke("Hi, I'm Alice.")
418
- # result2 = agent.invoke("What's my name?", messages: result1[:messages])
419
- # @example Suspend / resume flow
420
- # result = agent.invoke("Perform task X")
421
- # if result[:suspended]
422
- # result = agent.resume(result[:checkpoint], approved: true)
423
- # end
424
- # puts result[:output]
425
- # @example With InvocationContext (deadline-based timeout)
426
- # ctx = Phronomy::InvocationContext.new(
427
- # thread_id: "conv-123",
428
- # deadline: Phronomy::Concurrency::Deadline.in(30),
429
- # task_id: SecureRandom.uuid
430
- # )
431
- # result = MyAgent.new.invoke("Hello", invocation_context: ctx)
432
- # @api public
433
- def invoke(input, messages: [], thread_id: nil, config: {}, invocation_context: nil)
434
- if invocation_context
435
- thread_id, config = _apply_invocation_context(thread_id, config, invocation_context)
436
- end
437
- _check_scheduler_reentrancy(:invoke, :invoke_async)
438
-
439
- trace("agent.invoke", input: input, **_build_caller_meta(config)) do |_span|
440
- result = invoke_async(
441
- input,
442
- messages: messages,
443
- thread_id: thread_id,
444
- config: config
445
- ).wait_result
446
- [result, result[:usage]]
447
- end
448
- end
449
-
450
- # Invokes this agent asynchronously and returns a {Phronomy::Task}.
451
- #
452
- # This is the primary async entry point. {#invoke} is a synchronous wrapper
453
- # that calls this method and blocks the caller until the task completes.
454
- # Calling {#invoke} from inside an active scheduler task raises
455
- # {Phronomy::SchedulerReentrancyError}; use +invoke_async+ directly in that
456
- # context.
457
- #
458
- # The task is registered with the Runtime task registry so {Runtime#shutdown}
459
- # drains in-flight invocations before process exit.
460
- #
461
- # @example
462
- # task = agent.invoke_async("Hello!")
463
- # result = task.wait_result # => { output: "...", messages: [...], usage: ... }
464
- #
465
- # @param input [String, Hash]
466
- # @param messages [Array]
467
- # @param thread_id [String, nil]
468
- # @param config [Hash]
469
- # @param invocation_context [Phronomy::InvocationContext, nil]
470
- # @return [Phronomy::Task]
471
- # @api public
472
- def invoke_async(input, messages: [], thread_id: nil, config: {},
473
- invocation_context: nil, on_tool_approval_required: nil)
474
- if invocation_context
475
- thread_id, config = _apply_invocation_context(thread_id, config, invocation_context)
476
- end
477
- result_task = Phronomy::Task.deferred(name: "agent-#{(self.class.name || "anonymous").downcase}-async")
478
- approval_snapshot = _approval_configuration_snapshot(on_tool_approval_required)
479
- _start_invocation(
480
- result_task, input,
481
- messages: messages, thread_id: thread_id, config: config,
482
- approval_snapshot: approval_snapshot
483
- )
484
- result_task
485
- end
486
-
487
- # Invokes this agent asynchronously and delivers stream events from the
488
- # Runtime-owned EventLoop thread.
489
- #
490
- # The callback must return quickly. Blocking I/O, synchronous Agent calls,
491
- # sleep, and heavy CPU work must be delegated by the Application.
492
- #
493
- # @return [Phronomy::Task] final invocation result
494
- # @api public
495
- def stream_async(input, messages: [], thread_id: nil, config: {},
496
- invocation_context: nil, on_tool_approval_required: nil, &block)
497
- raise ArgumentError, "stream_async requires a block" unless block
498
-
499
- if invocation_context
500
- thread_id, config = _apply_invocation_context(thread_id, config, invocation_context)
501
- end
502
-
503
- result_task = Phronomy::Task.deferred(
504
- name: "agent-#{(self.class.name || "anonymous").downcase}-stream-async"
505
- )
506
- approval_snapshot = _approval_configuration_snapshot(on_tool_approval_required)
507
- _start_invocation(
508
- result_task,
509
- input,
510
- messages: messages,
511
- thread_id: thread_id,
512
- config: config,
513
- approval_snapshot: approval_snapshot,
514
- mode: :stream,
515
- on_event: block
516
- )
517
- result_task
518
- end
519
-
520
- # Synchronous wrapper around {#stream_async}.
521
- #
522
- # Stream callbacks execute on the EventLoop thread, not on the thread that
523
- # calls this method. This method only blocks while waiting for the final Task.
524
- # @yield [Phronomy::Agent::StreamEvent]
525
- # @return [Hash] same result shape as #invoke
526
- # @api public
527
- def stream(input, messages: [], thread_id: nil, config: {},
528
- invocation_context: nil, on_tool_approval_required: nil, &block)
529
- raise ArgumentError, "stream requires a block" unless block
530
-
531
- if invocation_context
532
- thread_id, config = _apply_invocation_context(thread_id, config, invocation_context)
533
- end
534
- _check_scheduler_reentrancy(:stream, :stream_async)
535
-
536
- trace("agent.stream", input: input, **_build_caller_meta(config)) do |_span|
537
- result = stream_async(
538
- input,
539
- messages: messages,
540
- thread_id: thread_id,
541
- config: config,
542
- on_tool_approval_required: on_tool_approval_required,
543
- &block
544
- ).wait_result
545
- [result, result[:usage]]
546
- end
547
- end
548
-
549
389
  # @deprecated The context version cache has been removed. Returns nil.
550
390
  # Retained for backward compatibility with callers using safe navigation (+&.reset+).
551
391
  # @api private
@@ -761,50 +601,6 @@ module Phronomy
761
601
  end
762
602
  protected :instance_knowledge_chunks
763
603
 
764
- # Starts one AgentInvocation and resolves +result_task+ from that session.
765
- # Phronomy translates the adapter's final error but never starts another
766
- # AgentInvocation automatically.
767
- # @api private
768
- def _start_invocation(result_task, input, messages:, thread_id:, config:,
769
- approval_snapshot:, mode: :invoke, on_event: nil)
770
- effective_config = thread_id ? config.merge(thread_id: thread_id) : config
771
- check_cancellation!(effective_config, "invocation cancelled")
772
- runtime = Phronomy::Runtime.instance
773
- event_loop = runtime.event_loop
774
- session = Agent::AgentInvocationSessionBuilder.build(
775
- agent: self,
776
- input: input,
777
- messages: messages,
778
- config: effective_config,
779
- approval_policy: approval_snapshot[:policy],
780
- approval_listener: approval_snapshot[:listener],
781
- mode: mode,
782
- on_event: on_event,
783
- runtime: runtime
784
- )
785
- callback_error_policy =
786
- Phronomy.configuration.stream_callback_error_policy
787
- source_task = Phronomy::Task.deferred(name: "#{result_task.name}-source")
788
- source_task.on_complete do |invocation, error|
789
- _handle_agent_completion(
790
- result_task: result_task,
791
- invocation: invocation,
792
- error: error,
793
- mode: mode,
794
- listener: on_event,
795
- event_loop: event_loop,
796
- callback_error_policy: callback_error_policy
797
- )
798
- end
799
-
800
- # Register completion handling before EventLoop admission. Otherwise an
801
- # immediately finishing session can complete source_task before the
802
- # callback is installed, causing Task#on_complete to run on this thread.
803
- event_loop.register(session, completion: source_task)
804
- rescue => e
805
- _fail_result_task(result_task, e)
806
- end
807
-
808
604
  def _complete_result_task(task, result)
809
605
  task.backend.unblock(result, nil)
810
606
  task.transition!(:completed, value: result)
@@ -821,83 +617,6 @@ module Phronomy
821
617
  translated
822
618
  end
823
619
 
824
- # Completes one Agent execution interval. Execution failures and
825
- # Application callback failures are deliberately handled in separate
826
- # exception domains.
827
- def _handle_agent_completion(result_task:, invocation:, error:, mode:, listener:,
828
- event_loop:, callback_error_policy:)
829
- if mode == :stream && !event_loop.current?
830
- completion_error = error || Phronomy::Error.new(
831
- "Stream completion occurred outside the EventLoop"
832
- )
833
- _fail_result_task(result_task, _translated_error(completion_error))
834
- return
835
- end
836
-
837
- result = nil
838
- execution_error = nil
839
- begin
840
- raise error if error
841
-
842
- result = _extract_invoke_result(invocation)
843
- rescue => e
844
- execution_error = _translated_error(e)
845
- end
846
-
847
- if execution_error
848
- if mode == :stream
849
- event = StreamEvent.new(
850
- type: :error,
851
- payload: {error: execution_error}
852
- )
853
- callback_error = _deliver_stream_event(listener, event)
854
- if callback_error
855
- _report_stream_callback_error(
856
- callback_error,
857
- event: event,
858
- invocation_id: invocation&.id,
859
- callback_error_policy: callback_error_policy
860
- )
861
- end
862
- end
863
-
864
- # An Application failure while consuming :error never replaces the
865
- # original Agent/LLM/Tool/Runtime failure.
866
- _fail_result_task(result_task, execution_error)
867
- return
868
- end
869
-
870
- unless mode == :stream
871
- _complete_result_task(result_task, result)
872
- return
873
- end
874
-
875
- event = _build_stream_terminal_event(result)
876
- callback_error = _deliver_stream_event(listener, event)
877
- unless callback_error
878
- _complete_result_task(result_task, result)
879
- return
880
- end
881
-
882
- _report_stream_callback_error(
883
- callback_error,
884
- event: event,
885
- invocation_id: invocation&.id,
886
- callback_error_policy: callback_error_policy
887
- )
888
-
889
- if callback_error_policy == :fail_task
890
- wrapped = _build_stream_callback_error(
891
- event_type: event.type,
892
- callback_error: callback_error,
893
- result: result
894
- )
895
- _fail_result_task(result_task, wrapped)
896
- else
897
- _complete_result_task(result_task, result)
898
- end
899
- end
900
-
901
620
  def _build_stream_terminal_event(result)
902
621
  if result[:suspended]
903
622
  StreamEvent.new(
@@ -1026,64 +745,6 @@ module Phronomy
1026
745
  end
1027
746
  public :approve_async
1028
747
 
1029
- # Parent completion handling is installed before EventLoop registration,
1030
- # and the parent session is registered before child sessions so immediate
1031
- # child events cannot be lost.
1032
- # @api private
1033
- def _start_approval_resume(result_task, invocation, approved:, config:)
1034
- invocation.merge_config!(config)
1035
- invocation.begin_approval_resume!(approved: approved)
1036
- runtime = Phronomy::Runtime.instance
1037
- event_loop = runtime.event_loop
1038
- source_task = Phronomy::Task.deferred(
1039
- name: "#{result_task.name}-source"
1040
- )
1041
- parent_session = Agent::AgentInvocationSessionBuilder.build_for_resume(
1042
- agent_invocation: invocation,
1043
- resume_event: :resume,
1044
- resume_phase: :suspended,
1045
- runtime: runtime
1046
- )
1047
- stream_listener = invocation.stream_listener
1048
- mode = stream_listener ? :stream : :invoke
1049
- callback_error_policy =
1050
- Phronomy.configuration.stream_callback_error_policy
1051
-
1052
- source_task.on_complete do |completed_invocation, error|
1053
- _handle_agent_completion(
1054
- result_task: result_task,
1055
- invocation: completed_invocation,
1056
- error: error,
1057
- mode: mode,
1058
- listener: stream_listener,
1059
- event_loop: event_loop,
1060
- callback_error_policy: callback_error_policy
1061
- )
1062
- end
1063
-
1064
- # The parent must exist before any child can post an immediate result.
1065
- event_loop.register(parent_session, completion: source_task)
1066
-
1067
- invocation.tool_invocations.each do |child|
1068
- child_session = if child.awaiting_approval?
1069
- Agent::ToolInvocationSessionBuilder.build_for_resume(
1070
- tool_invocation: child,
1071
- resume_event: approved ? :approve : :reject,
1072
- resume_phase: :awaiting_approval,
1073
- runtime: runtime
1074
- )
1075
- elsif !approved && child.authorized?
1076
- Agent::ToolInvocationSessionBuilder.build_for_resume(
1077
- tool_invocation: child,
1078
- resume_event: :cancel,
1079
- resume_phase: :authorized,
1080
- runtime: runtime
1081
- )
1082
- end
1083
- _register_tool_invocation_session(event_loop, runtime, child, child_session) if child_session
1084
- end
1085
- end
1086
-
1087
748
  def _extract_invoke_result(invocation)
1088
749
  if invocation.phase == :suspended
1089
750
  request = invocation.approval_request
@@ -1106,23 +767,6 @@ module Phronomy
1106
767
  end
1107
768
  end
1108
769
 
1109
- def _register_tool_invocation_session(event_loop, runtime, child, session)
1110
- completion = Phronomy::Task.deferred(name: "tool-session:#{child.id}")
1111
- completion.on_complete do |_result, error|
1112
- next unless error
1113
-
1114
- child.mark_framework_failed!(error)
1115
- runtime.event_loop.post(
1116
- Phronomy::Event.new(
1117
- type: :tool_failed,
1118
- target_id: child.parent_agent_invocation_id,
1119
- payload: {tool_invocation_id: child.id}
1120
- )
1121
- )
1122
- end
1123
- event_loop.register(session, completion: completion)
1124
- end
1125
-
1126
770
  def _dispatch_tool_approval_notification(invocation, request)
1127
771
  listener = invocation.approval_listener
1128
772
  return unless listener
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- VERSION = "0.15.0"
4
+ VERSION = "0.15.1"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: phronomy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.15.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Raizo T.C.S
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-08-02 00:00:00.000000000 Z
11
+ date: 2026-08-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby_llm
@@ -78,46 +78,6 @@ dependencies:
78
78
  - - "~>"
79
79
  - !ruby/object:Gem::Version
80
80
  version: '1.0'
81
- - !ruby/object:Gem::Dependency
82
- name: faraday
83
- requirement: !ruby/object:Gem::Requirement
84
- requirements:
85
- - - ">="
86
- - !ruby/object:Gem::Version
87
- version: '2'
88
- - - "<"
89
- - !ruby/object:Gem::Version
90
- version: '3'
91
- type: :runtime
92
- prerelease: false
93
- version_requirements: !ruby/object:Gem::Requirement
94
- requirements:
95
- - - ">="
96
- - !ruby/object:Gem::Version
97
- version: '2'
98
- - - "<"
99
- - !ruby/object:Gem::Version
100
- version: '3'
101
- - !ruby/object:Gem::Dependency
102
- name: event_stream_parser
103
- requirement: !ruby/object:Gem::Requirement
104
- requirements:
105
- - - ">="
106
- - !ruby/object:Gem::Version
107
- version: '1'
108
- - - "<"
109
- - !ruby/object:Gem::Version
110
- version: '2'
111
- type: :runtime
112
- prerelease: false
113
- version_requirements: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: '1'
118
- - - "<"
119
- - !ruby/object:Gem::Version
120
- version: '2'
121
81
  description: Phronomy is a Ruby AI agent framework that provides composable building
122
82
  blocks — Agents, Workflows, Tools, Filters, and Tracing — for building AI agents
123
83
  in Ruby. Powered by RubyLLM for LLM abstraction.