phronomy 0.16.0 → 0.18.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 +151 -1134
- data/CONTRIBUTING.md +28 -16
- data/README.md +69 -1249
- 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 +26 -6
- data/benchmark/bench_tool_schema.rb +2 -35
- data/docs/changelog/0.14-and-earlier.md +1137 -0
- data/docs/decisions/005-static-knowledge-class-level-cache.md +12 -1
- data/docs/decisions/008-orchestrator-uses-os-threads.md +46 -48
- data/docs/decisions/010-cooperative-first-concurrency.md +155 -228
- 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/docs/features.md +87 -0
- data/docs/getting-started.md +351 -0
- data/docs/migrations/0.15.md +35 -0
- data/docs/migrations/0.16.md +43 -0
- data/docs/runtime-and-concurrency.md +258 -0
- data/examples/workflows/generic_task_event_mapping.rb +14 -6
- data/lib/phronomy/agent/agent_invocation.rb +2 -36
- data/lib/phronomy/agent/agent_invocation_session_builder.rb +157 -94
- data/lib/phronomy/agent/agent_root.rb +1 -2
- data/lib/phronomy/agent/async_event_api.rb +3 -3
- data/lib/phronomy/agent/base.rb +170 -333
- data/lib/phronomy/agent/context/capability/base.rb +177 -298
- 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 +6 -7
- 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 +48 -138
- data/lib/phronomy/agent/token_budget_resolver.rb +5 -4
- data/lib/phronomy/agent/tool_executor.rb +29 -71
- data/lib/phronomy/agent/tool_invocation.rb +180 -336
- data/lib/phronomy/agent/tool_invocation_session_builder.rb +55 -161
- data/lib/phronomy/agent.rb +6 -10
- data/lib/phronomy/configuration.rb +4 -171
- data/lib/phronomy/diagnostics.rb +12 -41
- data/lib/phronomy/engine/concurrency/async_queue.rb +5 -188
- data/lib/phronomy/engine/concurrency/cancellation_scope.rb +6 -7
- data/lib/phronomy/engine/concurrency/cancellation_token.rb +51 -79
- data/lib/phronomy/engine/concurrency/deadline.rb +2 -3
- data/lib/phronomy/engine/concurrency/offload_pool.rb +696 -0
- data/lib/phronomy/engine/concurrency/pool_registry.rb +5 -5
- data/lib/phronomy/engine/event_loop.rb +89 -190
- data/lib/phronomy/engine/runtime/timer_queue.rb +48 -71
- data/lib/phronomy/engine/runtime/timer_service.rb +13 -21
- data/lib/phronomy/engine/runtime.rb +49 -377
- data/lib/phronomy/engine/task.rb +136 -277
- data/lib/phronomy/llm_adapter/base.rb +14 -14
- data/lib/phronomy/llm_adapter/ruby_llm.rb +3 -4
- data/lib/phronomy/llm_adapter.rb +2 -2
- data/lib/phronomy/llm_context_window/token_budget.rb +8 -79
- data/lib/phronomy/metrics.rb +15 -30
- data/lib/phronomy/multi_agent/fan_out_invocation.rb +146 -0
- data/lib/phronomy/multi_agent/fan_out_session_builder.rb +125 -0
- data/lib/phronomy/multi_agent/handoff.rb +1 -0
- data/lib/phronomy/multi_agent/orchestrator.rb +252 -256
- data/lib/phronomy/multi_agent/team_coordinator.rb +44 -133
- data/lib/phronomy/testing/eval/comparison.rb +23 -0
- data/lib/phronomy/testing/eval/dataset.rb +27 -0
- data/lib/phronomy/testing/eval/eval_case.rb +13 -0
- data/lib/phronomy/testing/eval/eval_result.rb +16 -0
- data/lib/phronomy/testing/eval/metrics.rb +43 -0
- data/lib/phronomy/testing/eval/runner.rb +52 -0
- data/lib/phronomy/testing/eval/scorer/base.rb +15 -0
- data/lib/phronomy/testing/eval/scorer/exact_match.rb +25 -0
- data/lib/phronomy/testing/eval/scorer/includes_scorer.rb +25 -0
- data/lib/phronomy/testing/eval/scorer/llm_judge.rb +46 -0
- data/lib/phronomy/testing/eval/scorer.rb +10 -0
- data/lib/phronomy/testing/eval.rb +9 -0
- data/lib/phronomy/testing/fake_clock.rb +6 -53
- data/lib/phronomy/testing.rb +2 -6
- data/lib/phronomy/tools/agent.rb +141 -6
- data/lib/phronomy/vector_store/async_backend.rb +21 -17
- data/lib/phronomy/vector_store/base.rb +2 -2
- data/lib/phronomy/vector_store/embeddings/base.rb +6 -5
- data/lib/phronomy/vector_store/in_memory.rb +2 -2
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/workflow_runner.rb +2 -4
- data/lib/phronomy.rb +7 -121
- data/scripts/api_snapshot.rb +4 -15
- metadata +24 -38
- 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/engine/concurrency/blocking_adapter_pool.rb +0 -561
- data/lib/phronomy/engine/runtime/deterministic_scheduler.rb +0 -439
- data/lib/phronomy/engine/runtime/fake_scheduler.rb +0 -165
- data/lib/phronomy/engine/runtime/runtime_metrics.rb +0 -116
- data/lib/phronomy/engine/runtime/scheduler.rb +0 -98
- data/lib/phronomy/engine/runtime/scheduler_timer_adapter.rb +0 -79
- data/lib/phronomy/engine/runtime/task_registry.rb +0 -95
- data/lib/phronomy/engine/runtime/thread_scheduler.rb +0 -30
- data/lib/phronomy/engine/task/backend.rb +0 -80
- data/lib/phronomy/engine/task/deferred_backend.rb +0 -73
- data/lib/phronomy/engine/task/fiber_backend.rb +0 -157
- data/lib/phronomy/engine/task/immediate_backend.rb +0 -89
- data/lib/phronomy/engine/task/mapped_backend.rb +0 -90
- data/lib/phronomy/engine/task/thread_backend.rb +0 -84
- data/lib/phronomy/engine/task_group.rb +0 -265
- data/lib/phronomy/eval/comparison.rb +0 -47
- data/lib/phronomy/eval/dataset.rb +0 -45
- data/lib/phronomy/eval/eval_case.rb +0 -17
- data/lib/phronomy/eval/eval_result.rb +0 -29
- data/lib/phronomy/eval/metrics.rb +0 -66
- data/lib/phronomy/eval/runner.rb +0 -94
- data/lib/phronomy/eval/scorer/base.rb +0 -22
- data/lib/phronomy/eval/scorer/exact_match.rb +0 -31
- data/lib/phronomy/eval/scorer/includes_scorer.rb +0 -32
- data/lib/phronomy/eval/scorer/llm_judge.rb +0 -72
- data/lib/phronomy/eval/scorer.rb +0 -9
- data/lib/phronomy/eval.rb +0 -7
- 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/lib/phronomy/testing/fake_scheduler.rb +0 -104
- data/lib/phronomy/testing/scheduler_helpers.rb +0 -68
|
@@ -2,104 +2,81 @@
|
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
4
|
class Runtime
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# Replaces the pattern of spawning one +Thread.new { sleep(t); callback }+
|
|
8
|
-
# per deadline. Any number of timers share a single background thread that
|
|
9
|
-
# sleeps until the earliest pending deadline.
|
|
10
|
-
#
|
|
11
|
-
# Use {#schedule} to register a one-shot callback; call {#shutdown} when the
|
|
12
|
-
# queue is no longer needed (e.g. on process exit) to stop the background
|
|
13
|
-
# thread cleanly.
|
|
5
|
+
# Threadless monotonic timer heap driven by EventLoop.
|
|
14
6
|
class TimerQueue
|
|
15
|
-
# @param clock [#call] zero-argument callable that returns the current
|
|
16
|
-
# monotonic time in seconds (defaults to +Process::CLOCK_MONOTONIC+).
|
|
17
|
-
# Override in tests to inject a fake clock.
|
|
18
|
-
# @api private
|
|
19
7
|
def initialize(clock: -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) })
|
|
20
8
|
@clock = clock
|
|
21
|
-
@heap = []
|
|
9
|
+
@heap = []
|
|
22
10
|
@mutex = Mutex.new
|
|
23
|
-
@cond = ConditionVariable.new
|
|
24
11
|
@stopped = false
|
|
25
|
-
@
|
|
26
|
-
|
|
12
|
+
@wake = nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Installs a lightweight wake callback used when a newly scheduled timer
|
|
16
|
+
# may change EventLoop's current wait deadline.
|
|
17
|
+
def wake_with(&block)
|
|
18
|
+
@mutex.synchronize { @wake = block }
|
|
19
|
+
self
|
|
27
20
|
end
|
|
28
21
|
|
|
29
|
-
# Schedule a one-shot callback to fire after +seconds+ from now.
|
|
30
|
-
#
|
|
31
|
-
# @param seconds [Numeric] delay before the callback fires
|
|
32
|
-
# @yield called (in the timer thread) when the deadline is reached
|
|
33
|
-
# @return [self]
|
|
34
|
-
# @api private
|
|
35
22
|
def schedule(seconds:, &callback)
|
|
23
|
+
raise ArgumentError, "schedule requires a block" unless callback
|
|
24
|
+
|
|
36
25
|
fire_at = @clock.call + seconds.to_f
|
|
26
|
+
wake = nil
|
|
37
27
|
@mutex.synchronize do
|
|
38
28
|
raise Phronomy::PoolShutdownError, "TimerQueue has been shut down" if @stopped
|
|
39
|
-
|
|
40
|
-
@
|
|
29
|
+
|
|
30
|
+
previous_first = @heap.first&.first
|
|
31
|
+
@heap << [fire_at, callback]
|
|
32
|
+
@heap.sort_by!(&:first)
|
|
33
|
+
wake = @wake if previous_first.nil? || fire_at < previous_first
|
|
41
34
|
end
|
|
35
|
+
wake&.call
|
|
42
36
|
self
|
|
43
37
|
end
|
|
44
38
|
|
|
45
|
-
#
|
|
46
|
-
|
|
47
|
-
# @return [self]
|
|
48
|
-
# @api private
|
|
49
|
-
def shutdown
|
|
39
|
+
# Seconds until the next timer is due, nil when no timers are pending.
|
|
40
|
+
def seconds_until_next
|
|
50
41
|
@mutex.synchronize do
|
|
51
|
-
@stopped
|
|
52
|
-
@
|
|
42
|
+
return nil if @stopped || @heap.empty?
|
|
43
|
+
[@heap.first.first - @clock.call, 0.0].max
|
|
53
44
|
end
|
|
54
|
-
@thread.join
|
|
55
|
-
self
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Number of pending (not yet fired) callbacks. Primarily for testing.
|
|
59
|
-
# @return [Integer]
|
|
60
|
-
# @api private
|
|
61
|
-
def pending_count
|
|
62
|
-
@mutex.synchronize { @heap.size }
|
|
63
45
|
end
|
|
64
46
|
|
|
65
|
-
|
|
47
|
+
# Executes all callbacks whose deadline is due. Must be called by EventLoop.
|
|
48
|
+
def fire_due
|
|
49
|
+
callbacks = @mutex.synchronize do
|
|
50
|
+
return 0 if @stopped
|
|
66
51
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
52
|
+
now = @clock.call
|
|
53
|
+
due_count = @heap.bsearch_index { |(fire_at, _)| fire_at > now } || @heap.length
|
|
54
|
+
@heap.shift(due_count).map(&:last)
|
|
55
|
+
end
|
|
71
56
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
callback&.call
|
|
78
|
-
rescue => e
|
|
79
|
-
Phronomy.configuration.logger&.error { "[TimerQueue] callback raised #{e.class}: #{e.message}" }
|
|
57
|
+
callbacks.each do |callback|
|
|
58
|
+
callback.call
|
|
59
|
+
rescue => error
|
|
60
|
+
Phronomy.configuration.logger&.error do
|
|
61
|
+
"[TimerQueue] callback raised #{error.class}: #{error.message}"
|
|
80
62
|
end
|
|
81
63
|
end
|
|
64
|
+
callbacks.length
|
|
82
65
|
end
|
|
83
66
|
|
|
84
|
-
def
|
|
85
|
-
@mutex.synchronize
|
|
86
|
-
|
|
87
|
-
return :stopped if @stopped
|
|
67
|
+
def pending_count
|
|
68
|
+
@mutex.synchronize { @heap.size }
|
|
69
|
+
end
|
|
88
70
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return @heap.shift[1]
|
|
96
|
-
else
|
|
97
|
-
remaining = fire_at - now
|
|
98
|
-
@cond.wait(@mutex, remaining)
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
71
|
+
def shutdown
|
|
72
|
+
wake = @mutex.synchronize do
|
|
73
|
+
return self if @stopped
|
|
74
|
+
@stopped = true
|
|
75
|
+
@heap.clear
|
|
76
|
+
@wake
|
|
102
77
|
end
|
|
78
|
+
wake&.call
|
|
79
|
+
self
|
|
103
80
|
end
|
|
104
81
|
end
|
|
105
82
|
end
|
|
@@ -2,38 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
4
|
class Runtime
|
|
5
|
-
# Lazy
|
|
6
|
-
#
|
|
7
|
-
# Returns a {SchedulerTimerAdapter} when the backing scheduler is a
|
|
8
|
-
# {DeterministicScheduler} (enabling virtual-time integration for the
|
|
9
|
-
# `:fiber` backend), or a standard {TimerQueue} (OS-thread backed) for all
|
|
10
|
-
# other schedulers.
|
|
11
|
-
# @api private
|
|
5
|
+
# Lazy owner of the Runtime's threadless timer queue.
|
|
12
6
|
class TimerService
|
|
13
|
-
|
|
14
|
-
# @api private
|
|
15
|
-
def initialize(scheduler)
|
|
16
|
-
@scheduler = scheduler
|
|
7
|
+
def initialize
|
|
17
8
|
@mutex = Mutex.new
|
|
18
9
|
@timer = nil
|
|
10
|
+
@waker = nil
|
|
19
11
|
end
|
|
20
12
|
|
|
21
|
-
# Returns (or lazily creates) the timer queue for this runtime.
|
|
22
|
-
# @return [TimerQueue, SchedulerTimerAdapter]
|
|
23
|
-
# @api private
|
|
24
13
|
def timer_queue
|
|
25
14
|
@mutex.synchronize do
|
|
26
|
-
@timer ||=
|
|
27
|
-
|
|
28
|
-
else
|
|
29
|
-
TimerQueue.new
|
|
15
|
+
@timer ||= TimerQueue.new.tap do |timer|
|
|
16
|
+
timer.wake_with(&@waker) if @waker
|
|
30
17
|
end
|
|
31
18
|
end
|
|
32
19
|
end
|
|
33
20
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
21
|
+
def wake_with(&block)
|
|
22
|
+
@mutex.synchronize do
|
|
23
|
+
@waker = block
|
|
24
|
+
@timer&.wake_with(&block)
|
|
25
|
+
end
|
|
26
|
+
self
|
|
27
|
+
end
|
|
28
|
+
|
|
37
29
|
def shutdown
|
|
38
30
|
@mutex.synchronize { @timer&.shutdown }
|
|
39
31
|
end
|