samagotchi 0.155.0 → 0.156.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/CHANGELOG.md +14 -1
- data/bin/chi +1 -1
- data/docs/architecture.md +10 -6
- data/docs/internals/anthropic.md +1 -1
- data/docs/internals/attached-context.md +3 -2
- data/docs/internals/background-tasks.md +1 -1
- data/docs/internals/engine.md +625 -935
- data/docs/internals/llm-context-forget.md +8 -3
- data/docs/internals/objectives.md +2 -2
- data/docs/internals/prompt-caching.md +4 -1
- data/docs/internals/remote-sessions.md +3 -1
- data/docs/internals/working-set.md +1 -1
- data/docs/sessions.md +2 -2
- data/docs/system-one.md +3 -2
- data/lib/samagotchi/bootstrap/config_writer.rb +1 -1
- data/lib/samagotchi/broadcast_command.rb +3 -3
- data/lib/samagotchi/bundle_command/try/sandbox.rb +1 -1
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +2 -2
- data/lib/samagotchi/bundles/system/manifest.yml +2 -2
- data/lib/samagotchi/child_move.rb +7 -51
- data/lib/samagotchi/children_status.rb +1 -1
- data/lib/samagotchi/cli/parent_wait.rb +1 -1
- data/lib/samagotchi/config.rb +17 -112
- data/lib/samagotchi/context_command.rb +1 -2
- data/lib/samagotchi/context_window.rb +3 -3
- data/lib/samagotchi/desktop/macos.rb +1 -1
- data/lib/samagotchi/engine/context_judge.rb +19 -11
- data/lib/samagotchi/engine/messages.rb +1 -1
- data/lib/samagotchi/engine/models.rb +5 -2
- data/lib/samagotchi/engine/objectives.rb +29 -11
- data/lib/samagotchi/engine/side_requests.rb +21 -11
- data/lib/samagotchi/engine/toolkit.rb +10 -9
- data/lib/samagotchi/engine/turn_end.rb +1 -1
- data/lib/samagotchi/engine/turn_helpers.rb +7 -7
- data/lib/samagotchi/engine/verify.rb +1 -1
- data/lib/samagotchi/engine.rb +14 -9
- data/lib/samagotchi/guardrails/targets.rb +24 -9
- data/lib/samagotchi/host_config.rb +72 -1
- data/lib/samagotchi/host_model.rb +2 -6
- data/lib/samagotchi/host_registry.rb +1 -1
- data/lib/samagotchi/kernel_loop.rb +46 -121
- data/lib/samagotchi/llm/anthropic_wire.rb +2 -2
- data/lib/samagotchi/llm/chat_loop.rb +34 -18
- data/lib/samagotchi/llm/native_backend.rb +1 -1
- data/lib/samagotchi/llm_context_apply.rb +54 -1
- data/lib/samagotchi/llm_context_notice.rb +1 -1
- data/lib/samagotchi/llm_context_strategy.rb +7 -9
- data/lib/samagotchi/llm_context_view.rb +9 -0
- data/lib/samagotchi/model_price.rb +4 -10
- data/lib/samagotchi/model_profile.rb +6 -6
- data/lib/samagotchi/model_settings.rb +37 -2
- data/lib/samagotchi/note_command.rb +1 -2
- data/lib/samagotchi/objectives_audit.rb +4 -3
- data/lib/samagotchi/participants.rb +2 -2
- data/lib/samagotchi/plugin/context.rb +1 -1
- data/lib/samagotchi/plugin/sessions.rb +2 -5
- data/lib/samagotchi/plugin/tool_spec.rb +5 -3
- data/lib/samagotchi/project_env.rb +1 -1
- data/lib/samagotchi/reasoning_history.rb +3 -3
- data/lib/samagotchi/reminder_carry.rb +1 -1
- data/lib/samagotchi/reprefill_meter.rb +45 -0
- data/lib/samagotchi/review_command.rb +1 -1
- data/lib/samagotchi/sampling_settings.rb +3 -3
- data/lib/samagotchi/self_report.rb +7 -7
- data/lib/samagotchi/session.rb +50 -7
- data/lib/samagotchi/session_access/local.rb +7 -5
- data/lib/samagotchi/session_access/remote.rb +17 -3
- data/lib/samagotchi/session_archive_command.rb +2 -2
- data/lib/samagotchi/session_chain.rb +7 -6
- data/lib/samagotchi/session_continue.rb +6 -5
- data/lib/samagotchi/session_delete_command.rb +1 -1
- data/lib/samagotchi/session_manager/delivery.rb +254 -0
- data/lib/samagotchi/session_manager/emptiness.rb +138 -0
- data/lib/samagotchi/session_manager/lifecycle.rb +274 -0
- data/lib/samagotchi/session_manager/listing.rb +189 -0
- data/lib/samagotchi/session_manager/removal.rb +190 -0
- data/lib/samagotchi/session_manager/spawn.rb +242 -0
- data/lib/samagotchi/session_manager/worker_command.rb +157 -0
- data/lib/samagotchi/session_manager.rb +26 -1540
- data/lib/samagotchi/session_metrics.rb +1 -1
- data/lib/samagotchi/sessions_command.rb +1 -1
- data/lib/samagotchi/share_command.rb +1 -1
- data/lib/samagotchi/starting_marker.rb +65 -0
- data/lib/samagotchi/steer_cut_after.rb +3 -3
- data/lib/samagotchi/system_one/recorder.rb +49 -0
- data/lib/samagotchi/terminal_ui/exit_lines.rb +1 -1
- data/lib/samagotchi/terminal_ui.rb +2 -6
- data/lib/samagotchi/testing/sessions.rb +1 -1
- data/lib/samagotchi/thinking.rb +3 -3
- data/lib/samagotchi/token_usage.rb +1 -1
- data/lib/samagotchi/tool_activity.rb +10 -4
- data/lib/samagotchi/tool_declarations.rb +2 -2
- data/lib/samagotchi/tool_output_cut.rb +17 -0
- data/lib/samagotchi/tool_runner.rb +7 -4
- data/lib/samagotchi/toolkit_command.rb +1 -1
- data/lib/samagotchi/tools/delegate.rb +10 -5
- data/lib/samagotchi/tools/delegate_result.rb +4 -4
- data/lib/samagotchi/tools/index_hint.rb +14 -0
- data/lib/samagotchi/tools/registry.rb +28 -11
- data/lib/samagotchi/tools/send_note.rb +4 -6
- data/lib/samagotchi/tools/tool_index.rb +52 -51
- data/lib/samagotchi/tools/tool_surface.rb +22 -8
- data/lib/samagotchi/turn_context.rb +2 -1
- data/lib/samagotchi/turn_stages/context_judge.rb +38 -36
- data/lib/samagotchi/turn_stages/objectives.rb +56 -52
- data/lib/samagotchi/turn_stages/toolkit_suggestion.rb +13 -17
- data/lib/samagotchi/turn_stages/verify_anchors.rb +9 -10
- data/lib/samagotchi/turn_stages/warmup.rb +14 -15
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/vision_support.rb +3 -3
- data/lib/samagotchi/web/app.rb +15 -8
- data/lib/samagotchi/web/participant_access.rb +2 -2
- data/lib/samagotchi/web/session_access_routes.rb +3 -3
- data/lib/samagotchi/web/session_control.rb +3 -3
- data/lib/samagotchi/web/session_create.rb +3 -3
- data/lib/samagotchi/web/session_hub.rb +1 -1
- data/lib/samagotchi/web/session_show.rb +3 -3
- data/lib/samagotchi/web/session_summary.rb +3 -4
- data/lib/samagotchi/worker.rb +55 -27
- data/lib/samagotchi/worker_boot.rb +184 -0
- data/lib/samagotchi/worker_signals.rb +1 -1
- data/lib/samagotchi/worker_task_wakes.rb +2 -2
- metadata +13 -1
|
@@ -228,13 +228,16 @@ module Samagotchi
|
|
|
228
228
|
private
|
|
229
229
|
|
|
230
230
|
# Pick the loop for +model+ (EffectiveModel); the chat loop is pointed
|
|
231
|
-
# at its host's adapter (one per host, kept for its cached model list)
|
|
231
|
+
# at its host's adapter (one per host, kept for its cached model list)
|
|
232
|
+
# and client (the window probe, the re-prefill's host).
|
|
232
233
|
def backend_for(model)
|
|
233
234
|
return @native_backend unless model.chat?
|
|
234
235
|
|
|
235
236
|
@chat_backend_mutex.synchronize do
|
|
236
|
-
@chat_backend ||= LLM::ChatLoop.new(kernel: @kernel,
|
|
237
|
+
@chat_backend ||= LLM::ChatLoop.new(kernel: @kernel, reprefill: @reprefill,
|
|
238
|
+
stable_length: ->(text) { @prompt_builder.stable_length(text) })
|
|
237
239
|
@chat_backend.adapter = @host_registry.adapter_for(model.entry)
|
|
240
|
+
@chat_backend.client = model.client
|
|
238
241
|
@chat_backend.session_id = session&.id
|
|
239
242
|
@chat_backend
|
|
240
243
|
end
|
|
@@ -9,8 +9,22 @@ module Samagotchi
|
|
|
9
9
|
# @host_registry, @session.
|
|
10
10
|
module ObjectivesPart
|
|
11
11
|
# The session's objectives as the page draws them, when that differs
|
|
12
|
-
# from the last announced (TurnStages::Objectives#announce)
|
|
13
|
-
|
|
12
|
+
# from the last announced (TurnStages::Objectives#announce), from any
|
|
13
|
+
# thread: end_turn's step, a command, Check this's thread, a note,
|
|
14
|
+
# a rollback. Under the event lock, which guards the last view: the
|
|
15
|
+
# sessions dir and the running turn's conversation read there, the
|
|
16
|
+
# event announced in the same step. It only informs: a failure is
|
|
17
|
+
# logged, never raised.
|
|
18
|
+
def announce_objectives(session = @session)
|
|
19
|
+
return unless session
|
|
20
|
+
|
|
21
|
+
synchronize_events do
|
|
22
|
+
event = @objectives_stage.announce(session, state_dir: session_state_dir, running: live_conversation)
|
|
23
|
+
announce(event) if event
|
|
24
|
+
end
|
|
25
|
+
rescue StandardError => e
|
|
26
|
+
Log.warn(:turn, "objectives_announce_failed", error: e.class.name, msg: e.message)
|
|
27
|
+
end
|
|
14
28
|
|
|
15
29
|
# Check this (ObjectivesCheck)'s one side request. Its usage is in the
|
|
16
30
|
# session's side line. Raises as ctx.ask_model's request does.
|
|
@@ -32,16 +46,20 @@ module Samagotchi
|
|
|
32
46
|
# The objectives' stage (TurnStages::Objectives).
|
|
33
47
|
attr_reader :objectives_stage
|
|
34
48
|
|
|
35
|
-
#
|
|
36
|
-
#
|
|
49
|
+
# However +turn+ ended (run_turn's ensure, after the turn flag is
|
|
50
|
+
# released): a completed turn's proposal cards shown
|
|
51
|
+
# (TurnStages::Objectives#finally). A card that fails is logged.
|
|
52
|
+
def show_objective_proposals(turn)
|
|
53
|
+
@objectives_stage.finally(turn).each { |card| show_card(**card) }
|
|
54
|
+
rescue StandardError => e
|
|
55
|
+
Log.warn(:turn, "objectives_proposal_failed", error: e.class.name, msg: e.message)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# The stage over this Engine's side asks (built in #initialize, once
|
|
59
|
+
# the host registry is). Each call takes its values: the recorder,
|
|
60
|
+
# the sessions dir, the running conversation.
|
|
37
61
|
def build_objectives_stage
|
|
38
|
-
TurnStages::Objectives.new(
|
|
39
|
-
side_judgments: @side_judgments, host_registry: @host_registry, state_dir: -> { session_state_dir },
|
|
40
|
-
recording: ->(client, judgment, session, mode:) { system_one_recording(client, judgment, session, mode: mode) },
|
|
41
|
-
runs: -> { system_one_runs }, synchronize: ->(&block) { synchronize_events(&block) },
|
|
42
|
-
announce: ->(event) { announce(event) }, running: -> { live_conversation },
|
|
43
|
-
show_card: ->(**card) { show_card(**card) }
|
|
44
|
-
)
|
|
62
|
+
TurnStages::Objectives.new(side_judgments: @side_judgments, host_registry: @host_registry)
|
|
45
63
|
end
|
|
46
64
|
end
|
|
47
65
|
end
|
|
@@ -6,6 +6,7 @@ module Samagotchi
|
|
|
6
6
|
# IdleClient, their usage in the session's side line, and System 1's
|
|
7
7
|
# recording of judged asks. Writes @side_client/@side_client_key (under
|
|
8
8
|
# @side_client_mutex); reads @metrics, @session, @host_registry.
|
|
9
|
+
# The recorder (SystemOne::Recorder) is the turn stages' recording seam.
|
|
9
10
|
module SideRequestsPart
|
|
10
11
|
# Count one side request (a recap, a plugin's ctx.ask_model: IdleClient's
|
|
11
12
|
# on_usage) in the session's side line, apart from its turns' totals.
|
|
@@ -52,24 +53,33 @@ module Samagotchi
|
|
|
52
53
|
|
|
53
54
|
private
|
|
54
55
|
|
|
55
|
-
# +client+ (nil for none) saving its asks as +judgment+'s runs
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
# A scratch session's asks aren't saved; a participant's turn is never
|
|
60
|
-
# judged, so it never comes here.
|
|
56
|
+
# +client+ (nil for none) saving its asks as +judgment+'s runs
|
|
57
|
+
# (SystemOne::Recorder#wrap, over a recorder built now): the
|
|
58
|
+
# guardrails' System 1 client. No client or a scratch session: the
|
|
59
|
+
# client as it is, with nothing read (no recorder built).
|
|
61
60
|
# @param mode [String, nil] the use's mode, for its runs (Recording.wrap)
|
|
62
61
|
def system_one_recording(client, judgment, session, mode: nil)
|
|
63
62
|
return client if client.nil? || session&.scratch
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
SystemOne::Recording.wrap(client, judgment: judgment, runs: system_one_runs, context: context, recorded_mode: mode)
|
|
67
|
-
rescue StandardError => e
|
|
68
|
-
Log.warn(:system_one, "system_one_recording_failed", error: e.class.name, msg: e.message.to_s[0, 200])
|
|
69
|
-
client
|
|
64
|
+
system_one_recorder.wrap(client, judgment, session, mode: mode)
|
|
70
65
|
end
|
|
71
66
|
private :system_one_recording
|
|
72
67
|
|
|
68
|
+
# What a turn stage saves its System 1 asks with (SystemOne::Recorder):
|
|
69
|
+
# this Engine's runs and the model it names now, read at this call and
|
|
70
|
+
# never kept, so each stage call sees the model as it is then (an
|
|
71
|
+
# outgoing ask after a before_turn hook's switch, the new one). Never
|
|
72
|
+
# raises: on a failure, SystemOne::Recorder::NONE (no runs, the plain
|
|
73
|
+
# client), said once per failure as before.
|
|
74
|
+
# @return [SystemOne::Recorder]
|
|
75
|
+
def system_one_recorder
|
|
76
|
+
SystemOne::Recorder.new(runs: system_one_runs, session_model: effective_model_name)
|
|
77
|
+
rescue StandardError => e
|
|
78
|
+
SystemOne::Recorder.failed(e)
|
|
79
|
+
SystemOne::Recorder::NONE
|
|
80
|
+
end
|
|
81
|
+
private :system_one_recorder
|
|
82
|
+
|
|
73
83
|
# Where this Engine's runs are saved; read each time, as the settings are.
|
|
74
84
|
def system_one_runs = SystemOne::Runs.for_sessions_dir(session_state_dir)
|
|
75
85
|
private :system_one_runs
|
|
@@ -22,10 +22,14 @@ module Samagotchi
|
|
|
22
22
|
# turn's staged tool sets and its toolkit apply.
|
|
23
23
|
def publish_tools! = @toolkit_binding.publish!
|
|
24
24
|
|
|
25
|
+
# That published Registry (ToolkitBinding#published): what a reader
|
|
26
|
+
# off the turn thread (/toolkit) reads of the tools.
|
|
27
|
+
def published_tools = @toolkit_binding.published
|
|
28
|
+
|
|
25
29
|
# The tools this session registered (Tools::Registry), declared or not.
|
|
26
30
|
attr_reader :tools
|
|
27
31
|
|
|
28
|
-
public :tools, :toolkit_kits, :resolved_toolkit, :toolkit_explained, :toolkit_summary
|
|
32
|
+
public :tools, :published_tools, :toolkit_kits, :resolved_toolkit, :toolkit_explained, :toolkit_summary
|
|
29
33
|
|
|
30
34
|
# A plugin's ctx.load_tools: stage the reachable tools +names+ names
|
|
31
35
|
# (Tools::ToolIndex#deferred_name, on the published Registry) that the
|
|
@@ -35,7 +39,7 @@ module Samagotchi
|
|
|
35
39
|
# @return [Array<String>] the wire names staged
|
|
36
40
|
def load_tools(names, bundle)
|
|
37
41
|
added = ToolLoads.added(@session&.messages)
|
|
38
|
-
published =
|
|
42
|
+
published = published_tools
|
|
39
43
|
surface = Tools::ToolSurface.for(published)
|
|
40
44
|
wanted = names.filter_map do |name|
|
|
41
45
|
wire = @tool_index.deferred_name(name, surface)
|
|
@@ -97,14 +101,11 @@ module Samagotchi
|
|
|
97
101
|
public :warm_toolkit_suggestion!
|
|
98
102
|
|
|
99
103
|
# The stage over this Engine's side asks, binding and tools (built in
|
|
100
|
-
# #build_tools_and_plugins, before the plugins load).
|
|
104
|
+
# #build_tools_and_plugins, before the plugins load). Its turn's start
|
|
105
|
+
# takes the recorder (#system_one_recorder) per call.
|
|
101
106
|
def build_toolkit_suggestion
|
|
102
|
-
TurnStages::ToolkitSuggestion.new(
|
|
103
|
-
|
|
104
|
-
tools: @tools, state_dir: -> { session_state_dir },
|
|
105
|
-
recording: ->(client, judgment, session, mode:) { system_one_recording(client, judgment, session, mode: mode) },
|
|
106
|
-
runs: -> { system_one_runs }
|
|
107
|
-
)
|
|
107
|
+
TurnStages::ToolkitSuggestion.new(side_judgments: @side_judgments, host_registry: @host_registry,
|
|
108
|
+
toolkit_binding: @toolkit_binding, tools: @tools)
|
|
108
109
|
end
|
|
109
110
|
private :build_toolkit_suggestion
|
|
110
111
|
end
|
|
@@ -117,7 +117,7 @@ module Samagotchi
|
|
|
117
117
|
# a display version of the answer (AnswerDisplay) and event[:refs]
|
|
118
118
|
# for refs on it (AnswerRefs).
|
|
119
119
|
session = turn.session
|
|
120
|
-
@objectives_stage.outgoing(turn, result) unless canceled || result.exhausted?
|
|
120
|
+
@objectives_stage.outgoing(turn, result, recorder: system_one_recorder) unless canceled || result.exhausted?
|
|
121
121
|
display = AnswerDisplay.new(session.messages)
|
|
122
122
|
refs = AnswerRefs.new(session.messages, cwd: session.working_directory)
|
|
123
123
|
after_turn = { type: :after_turn, status: canceled ? "canceled" : "completed",
|
|
@@ -7,7 +7,7 @@ module Samagotchi
|
|
|
7
7
|
# with, the used memories it publishes, and the turn-end warm-up of the
|
|
8
8
|
# next one (TurnStages::Warmup, built here). Writes @next_turn_waiting;
|
|
9
9
|
# reads @model_settings, @kernel, @prompt_warmup, @thinking_notices,
|
|
10
|
-
# @turn_state, @turn_warmup.
|
|
10
|
+
# @turn_state, @reminder_queue, @turn_warmup.
|
|
11
11
|
module TurnHelpersPart
|
|
12
12
|
# The turn's settings for the kernel (LLM::TurnSettings): vision,
|
|
13
13
|
# sampling and thinking, the thinking resolved before the hooks run.
|
|
@@ -40,13 +40,13 @@ module Samagotchi
|
|
|
40
40
|
private :turn_warmup
|
|
41
41
|
|
|
42
42
|
# The stage over the kernel and the Engine's PromptWarmup (built in
|
|
43
|
-
# #initialize, once the kernel
|
|
43
|
+
# #initialize, once the kernel and the reminder queue are). Its one
|
|
44
|
+
# callable is the system prompt, built late (side effects); the
|
|
45
|
+
# host's probe goes with each call (#next_turn_waiting, set after).
|
|
44
46
|
def build_turn_warmup
|
|
45
|
-
TurnStages::Warmup.new(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
reminders_due: -> { reminders_due? }, next_turn_waiting: -> { next_turn_waiting&.call }
|
|
49
|
-
)
|
|
47
|
+
TurnStages::Warmup.new(prompt_warmup: @prompt_warmup, kernel: @kernel, turn_state: @turn_state,
|
|
48
|
+
model_settings: @model_settings, reminder_queue: @reminder_queue,
|
|
49
|
+
system_prompt: ->(model) { system_prompt(model) })
|
|
50
50
|
end
|
|
51
51
|
private :build_turn_warmup
|
|
52
52
|
|
|
@@ -11,7 +11,7 @@ module Samagotchi
|
|
|
11
11
|
# The anchors' stage (TurnStages::VerifyAnchors).
|
|
12
12
|
attr_reader :verify_anchors
|
|
13
13
|
|
|
14
|
-
def build_verify_anchors = TurnStages::VerifyAnchors.new
|
|
14
|
+
def build_verify_anchors = TurnStages::VerifyAnchors.new
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
end
|
data/lib/samagotchi/engine.rb
CHANGED
|
@@ -27,6 +27,7 @@ require_relative "thinking_notices"
|
|
|
27
27
|
require_relative "cancellation_controller"
|
|
28
28
|
require_relative "context_window"
|
|
29
29
|
require_relative "kernel_loop"
|
|
30
|
+
require_relative "reprefill_meter"
|
|
30
31
|
require_relative "tools/builtins"
|
|
31
32
|
require_relative "tools/task_runtime"
|
|
32
33
|
require_relative "tools/tool_index"
|
|
@@ -55,6 +56,7 @@ require_relative "context_judge"
|
|
|
55
56
|
require_relative "objectives_audit"
|
|
56
57
|
require_relative "owner_prompt"
|
|
57
58
|
require_relative "side_judgments"
|
|
59
|
+
require_relative "system_one/recorder"
|
|
58
60
|
require_relative "toolkit_suggest"
|
|
59
61
|
require_relative "turn_stages/context_judge"
|
|
60
62
|
require_relative "turn_stages/objectives"
|
|
@@ -451,6 +453,9 @@ module Samagotchi
|
|
|
451
453
|
self.guardrail_state_dir = Session.default_state_dir
|
|
452
454
|
# The turn-end warm-up; the kernel asks it for a slot pin per request.
|
|
453
455
|
@prompt_warmup = PromptWarmup.new
|
|
456
|
+
# One re-prefill baseline for both loops (the native kernel's and the
|
|
457
|
+
# chat loop's requests), reset by the warm-up (KernelLoop#warmed_up!).
|
|
458
|
+
@reprefill = ReprefillMeter.new
|
|
454
459
|
services = KernelLoop::Services.new(
|
|
455
460
|
hooks: @hooks, tools: @tools, reminder_store: @reminder_store, muted_memory_names: @muted_memory_names,
|
|
456
461
|
# ask_user_question blocks on the Engine's question flow (TUI/Web answer it).
|
|
@@ -466,8 +471,8 @@ module Samagotchi
|
|
|
466
471
|
)
|
|
467
472
|
# A given kernel (specs) gets the same services as the one built here.
|
|
468
473
|
@kernel = kernel || KernelLoop.new(client: @client, profile: @model_binding.given_profile,
|
|
469
|
-
model_name: effective_model_name, services: services)
|
|
470
|
-
@kernel.attach(services) if kernel
|
|
474
|
+
model_name: effective_model_name, services: services, reprefill: @reprefill)
|
|
475
|
+
@kernel.attach(services, reprefill: @reprefill) if kernel
|
|
471
476
|
sync_kernel_client!
|
|
472
477
|
sync_model_key!
|
|
473
478
|
# What a hook can do beyond reading its event (event[:notify],
|
|
@@ -727,7 +732,7 @@ module Samagotchi
|
|
|
727
732
|
event[:verdict] = verdict if verdict
|
|
728
733
|
event[:revises] = revises.to_h if revises
|
|
729
734
|
emit_working_set(turn.on_event, turn.session, turn.session.messages)
|
|
730
|
-
|
|
735
|
+
announce_objectives(turn.session)
|
|
731
736
|
turn.session.status = Session::STATUS_IDLE
|
|
732
737
|
record_last_turn(turn.session, outcome, seconds, turn.origin, event, limit: exhausted ? turn.limit : nil)
|
|
733
738
|
emit_event(turn.on_event, turn.tag(event))
|
|
@@ -911,7 +916,7 @@ module Samagotchi
|
|
|
911
916
|
pending_input: pending_input)
|
|
912
917
|
publish_used_memories(session, on_event)
|
|
913
918
|
complete_turn(turn, result)
|
|
914
|
-
@turn_warmup.after(turn, result)
|
|
919
|
+
@turn_warmup.after(turn, result, next_turn_waiting: next_turn_waiting)
|
|
915
920
|
result
|
|
916
921
|
rescue Interrupt => e
|
|
917
922
|
# After the end event (in the after_turn/session_end hooks) the
|
|
@@ -941,7 +946,7 @@ module Samagotchi
|
|
|
941
946
|
@context_judge.show_cards_after_turn
|
|
942
947
|
# Only a completed turn shows its proposal cards; any other's are
|
|
943
948
|
# dropped, so the next turn does not show them either.
|
|
944
|
-
|
|
949
|
+
show_objective_proposals(turn)
|
|
945
950
|
end
|
|
946
951
|
end
|
|
947
952
|
|
|
@@ -954,7 +959,7 @@ module Samagotchi
|
|
|
954
959
|
# A participant's Verify fresh child: every turn is theirs (their
|
|
955
960
|
# author, boundary and cap), whoever started it.
|
|
956
961
|
origin = session.verify_fresh.origin_for(origin) if session.respond_to?(:verify_fresh) && session.verify_fresh
|
|
957
|
-
verify = @verify_anchors.verified(session, VerifyAnchor.from(verify))
|
|
962
|
+
verify = @verify_anchors.verified(session, VerifyAnchor.from(verify), state_dir: session_state_dir)
|
|
958
963
|
# Track the active session for recap and status snapshot.
|
|
959
964
|
@session = session
|
|
960
965
|
# status is turn state: running now, idle again before the turn's end
|
|
@@ -1044,7 +1049,7 @@ module Samagotchi
|
|
|
1044
1049
|
# On mode's ask starts here too; the turn joins it before
|
|
1045
1050
|
# apply_toolkit! below, so its latency hides behind the probes and
|
|
1046
1051
|
# the init wait.
|
|
1047
|
-
@toolkit_suggestion.start(turn)
|
|
1052
|
+
@toolkit_suggestion.start(turn, recorder: system_one_recorder)
|
|
1048
1053
|
|
|
1049
1054
|
# The window and the profile probes run after run_turn's probe-cancel
|
|
1050
1055
|
# swap, so a Stop cuts them; a failure ends the turn as turn_failed.
|
|
@@ -1081,8 +1086,8 @@ module Samagotchi
|
|
|
1081
1086
|
@toolkit_suggestion.settle(turn)
|
|
1082
1087
|
# The candidate judge: its asks run beside the turn; on mode's
|
|
1083
1088
|
# attaches reach the model at the next between-turn delivery.
|
|
1084
|
-
@context_judge.start(turn)
|
|
1085
|
-
@objectives_stage.incoming(turn)
|
|
1089
|
+
@context_judge.start(turn, recorder: system_one_recorder)
|
|
1090
|
+
@objectives_stage.incoming(turn, recorder: system_one_recorder)
|
|
1086
1091
|
apply_toolkit!(session, turn.on_event)
|
|
1087
1092
|
refresh_participants!(session)
|
|
1088
1093
|
# A Stop during the waits above: the turn ends here,
|
|
@@ -113,8 +113,10 @@ module Samagotchi
|
|
|
113
113
|
# @param model_name [String, nil] the bare model name, for a rule's models:
|
|
114
114
|
# @param registry [Tools::Registry, nil] the session's tools: a plugin
|
|
115
115
|
# tool's entry says what it acts on (its targets:)
|
|
116
|
-
# @param surface [Tools::ToolSurface, nil] the turn's:
|
|
117
|
-
#
|
|
116
|
+
# @param surface [Tools::ToolSurface, nil] the turn's: the entry is
|
|
117
|
+
# looked up in it (.entry_of) and call_tool's targets name what it
|
|
118
|
+
# reaches (nil: the registry's entry; call_tool's then raises
|
|
119
|
+
# Tools::MissingSurface)
|
|
118
120
|
def self.for(call, context, model_key: nil, model_name: nil, registry: nil, surface: nil)
|
|
119
121
|
tool = call[:name].to_s
|
|
120
122
|
base = context.cwd
|
|
@@ -143,7 +145,7 @@ module Samagotchi
|
|
|
143
145
|
given_cwd = given[:cwd].to_s.strip
|
|
144
146
|
cwd = File.expand_path(given_cwd, base) unless given_cwd.empty?
|
|
145
147
|
paths.concat(given[:paths].flat_map { |path| absolutes(path, cwd) })
|
|
146
|
-
entry = registry
|
|
148
|
+
entry = entry_of(tool, registry, surface)
|
|
147
149
|
args = given[:args] || call[:args] if entry && !entry.core? && call[:args].is_a?(Hash)
|
|
148
150
|
acts_as = given[:acts_as]
|
|
149
151
|
label = given[:label]
|
|
@@ -164,9 +166,12 @@ module Samagotchi
|
|
|
164
166
|
# logged). args: counts when it is a Hash; acts_as: is dropped (and
|
|
165
167
|
# logged) when it names a core tool or another bundle's tool, so a
|
|
166
168
|
# call can't pass for one and match (or reuse the approvals of) its
|
|
167
|
-
# rules. label: counts when it is one short line (.label_of).
|
|
169
|
+
# rules. label: counts when it is one short line (.label_of). A
|
|
170
|
+
# missing surface (Tools::MissingSurface) is raised, never taken as
|
|
171
|
+
# nothing: call_tool's acts_as would go missing, and with it the
|
|
172
|
+
# mcp_* rules on the call (fail-open).
|
|
168
173
|
def self.plugin_targets(call, registry, surface = nil)
|
|
169
|
-
entry =
|
|
174
|
+
entry = entry_of(call[:name], registry, surface)
|
|
170
175
|
none = { command: nil, paths: [], cwd: nil, acts_as: nil, args: nil, label: nil }
|
|
171
176
|
return none unless entry && !entry.core? && entry.targets
|
|
172
177
|
|
|
@@ -176,8 +181,10 @@ module Samagotchi
|
|
|
176
181
|
given = given.transform_keys(&:to_sym)
|
|
177
182
|
command = given[:command].to_s
|
|
178
183
|
{ command: command.empty? ? nil : command, paths: Array(given[:paths]).map(&:to_s), cwd: given[:cwd],
|
|
179
|
-
acts_as: acts_as_of(given[:acts_as], entry, registry), args: given[:args].is_a?(Hash) ? given[:args] : nil,
|
|
184
|
+
acts_as: acts_as_of(given[:acts_as], entry, registry, surface), args: given[:args].is_a?(Hash) ? given[:args] : nil,
|
|
180
185
|
label: label_of(given[:label], entry) }
|
|
186
|
+
rescue Tools::MissingSurface
|
|
187
|
+
raise
|
|
181
188
|
rescue StandardError => e
|
|
182
189
|
Log.warn(:plugins, "plugin_targets_failed", tool: call[:name].to_s, error: e.class.name,
|
|
183
190
|
msg: "#{call[:name]} targets failed: #{e.message}")
|
|
@@ -192,24 +199,32 @@ module Samagotchi
|
|
|
192
199
|
# name, ToolIndex#claimed), so the registry is the wrong owner test,
|
|
193
200
|
# and a bundle tool sharing an MCP tool's name would otherwise take
|
|
194
201
|
# call_tool calls of that tool out of its mcp_* rules.
|
|
195
|
-
def self.acts_as_of(name, entry, registry)
|
|
202
|
+
def self.acts_as_of(name, entry, registry, surface = nil)
|
|
196
203
|
name = name.to_s.strip
|
|
197
204
|
return nil if name.empty? || name == entry.name
|
|
198
205
|
|
|
199
|
-
other = registry
|
|
206
|
+
other = entry_of(name, registry, surface)
|
|
200
207
|
whose = if CORE_TOOLS.include?(name) || other&.core? then "a core tool"
|
|
201
208
|
elsif other && other.source != entry.source && entry.source != Tools::ToolIndex::SOURCE
|
|
202
209
|
"bundle #{other.source}'s tool"
|
|
203
210
|
end
|
|
204
211
|
return name unless whose
|
|
205
212
|
|
|
206
|
-
if first_drop?(registry, entry.name, name)
|
|
213
|
+
if first_drop?(registry || surface, entry.name, name)
|
|
207
214
|
Log.warn(:plugins, "plugin_acts_as_dropped", tool: entry.name, acts_as: name,
|
|
208
215
|
msg: "#{entry.name} targets: acts_as #{name} dropped: it is #{whose}")
|
|
209
216
|
end
|
|
210
217
|
nil
|
|
211
218
|
end
|
|
212
219
|
|
|
220
|
+
# The tool +name+ names: the turn's entry (+surface+'s registered),
|
|
221
|
+
# else +registry+'s (a gate without a turn's surface); nil for none.
|
|
222
|
+
# @return [Tools::Registry::Entry, nil]
|
|
223
|
+
def self.entry_of(name, registry, surface)
|
|
224
|
+
name = name.to_s
|
|
225
|
+
surface ? surface.registered[name] : registry&.[](name)
|
|
226
|
+
end
|
|
227
|
+
|
|
213
228
|
# The drops logged, per registry (a session's): each tool's acts_as
|
|
214
229
|
# once, not at every call (targets run several times a call).
|
|
215
230
|
DROPS_LOGGED = ObjectSpace::WeakKeyMap.new
|
|
@@ -8,7 +8,7 @@ require_relative "llm/dialect"
|
|
|
8
8
|
require_relative "reasoning_history"
|
|
9
9
|
|
|
10
10
|
module Samagotchi
|
|
11
|
-
# One config.yml hosts: entry, parsed and checked (
|
|
11
|
+
# One config.yml hosts: entry, parsed and checked (HostConfig.all
|
|
12
12
|
# holds them by lowercased name; HostRegistry builds its HostEntry from
|
|
13
13
|
# one).
|
|
14
14
|
#
|
|
@@ -28,6 +28,77 @@ module Samagotchi
|
|
|
28
28
|
super(**members.to_h { |m| [m, nil] }, models: {}, **fields)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
# config.yml's hosts: by lowercased name; an entry that is disabled or
|
|
32
|
+
# invalid (warned) is left out. No entry left: "default", from
|
|
33
|
+
# server.host/port.
|
|
34
|
+
# @return [Hash{String => HostConfig}]
|
|
35
|
+
def self.all(env: ENV, path: ConfigFile.global_path(env: env))
|
|
36
|
+
data = ConfigFile.read_yaml(env: env, path: path)
|
|
37
|
+
raw_hosts = data[ConfigFile::HOSTS_KEY] if data.is_a?(Hash)
|
|
38
|
+
|
|
39
|
+
normalized = {}
|
|
40
|
+
if raw_hosts.is_a?(Hash)
|
|
41
|
+
raw_hosts.each do |raw_name, raw_cfg|
|
|
42
|
+
host = parse(raw_name, raw_cfg)
|
|
43
|
+
normalized[host.name] = host if host
|
|
44
|
+
end
|
|
45
|
+
warn_duplicate_models(normalized)
|
|
46
|
+
end
|
|
47
|
+
normalized["default"] = default_host(data, env) if normalized.empty?
|
|
48
|
+
normalized
|
|
49
|
+
rescue StandardError => e
|
|
50
|
+
Log.error(:config, "hosts_config_failed", error: e.class.name, message: e.message)
|
|
51
|
+
{}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# The names (lowercased) of the hosts entries with enabled: false, which
|
|
55
|
+
# .all leaves out, from config.yml's hosts:.
|
|
56
|
+
# @return [Array<String>]
|
|
57
|
+
def self.disabled_names(env: ENV, path: ConfigFile.global_path(env: env))
|
|
58
|
+
data = ConfigFile.read_yaml(env: env, path: path)
|
|
59
|
+
raw = data[ConfigFile::HOSTS_KEY] if data.is_a?(Hash)
|
|
60
|
+
return [] unless raw.is_a?(Hash)
|
|
61
|
+
|
|
62
|
+
raw.filter_map { |name, cfg| name.to_s.strip.downcase if cfg.is_a?(Hash) && disabled?(cfg) }
|
|
63
|
+
rescue StandardError # degrade-ok: Config↔Log: Log resolves its file and level through Config
|
|
64
|
+
[]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# +raw+ parsed against config.yml's hosts (or +hosts+) and aliases.
|
|
68
|
+
# @return [ModelRef]
|
|
69
|
+
def self.model_ref(raw, env: ENV, path: ConfigFile.global_path(env: env), hosts: nil)
|
|
70
|
+
ModelRef.parse(raw, hosts: hosts || all(env: env, path: path), aliases: ConfigFile.model_aliases(env: env, path: path))
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# The host chi uses when hosts: names none: server.host/port.
|
|
74
|
+
def self.default_host(data, env)
|
|
75
|
+
opts = { file_data: data, env: env, cli_overrides: Config.cli_overrides }
|
|
76
|
+
host = Config.resolve("server.host", **opts).to_s.strip
|
|
77
|
+
host = "localhost" if host.empty?
|
|
78
|
+
port = Config.resolve("server.port", **opts).to_i
|
|
79
|
+
port = 8080 if port <= 0 || port > 65_535
|
|
80
|
+
new(name: "default", host: host, port: port)
|
|
81
|
+
end
|
|
82
|
+
private_class_method :default_host
|
|
83
|
+
|
|
84
|
+
# Two hosts declaring one id under hosts.<name>.models: a bare id goes
|
|
85
|
+
# where HostRegistry#host_for_model sends it (the default host when it
|
|
86
|
+
# declares the id, else the first in hosts: order); say so once.
|
|
87
|
+
def self.warn_duplicate_models(hosts)
|
|
88
|
+
declared = Hash.new { |h, k| h[k] = [] }
|
|
89
|
+
hosts.each { |name, cfg| cfg.models.each { |down, model| declared[down] << [name, model.id] } }
|
|
90
|
+
declared.each_value do |owners|
|
|
91
|
+
next if owners.size < 2
|
|
92
|
+
|
|
93
|
+
names = owners.map(&:first)
|
|
94
|
+
id = owners.first.last
|
|
95
|
+
goes = names.include?("default") ? "default" : "#{names.first} unless the default host lists it"
|
|
96
|
+
ConfigFile.warn_once "Warning: #{names.map { |n| "hosts.#{n}.models" }.join(" and ")} #{names.size == 2 ? "both" : "all"} " \
|
|
97
|
+
"declare #{id}; a bare #{id} goes to #{goes}"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
private_class_method :warn_duplicate_models
|
|
101
|
+
|
|
31
102
|
# A hosts: entry as written in config.yml,
|
|
32
103
|
# or nil when it is disabled (enabled: false) or invalid. An invalid
|
|
33
104
|
# one warns once, naming the entry, the way each reader of a setting
|
|
@@ -67,12 +67,8 @@ module Samagotchi
|
|
|
67
67
|
served == "any" || served.any? { |id| id.casecmp?(name.to_s.strip) }
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
def self.warn_once(message)
|
|
73
|
-
require_relative "config"
|
|
74
|
-
ConfigFile.warn_once(message)
|
|
75
|
-
end
|
|
70
|
+
# Config comes with model_price.
|
|
71
|
+
def self.warn_once(message) = ConfigFile.warn_once(message)
|
|
76
72
|
private_class_method :warn_once
|
|
77
73
|
|
|
78
74
|
# One host's rows as the listings show them, declared ids first, then
|
|
@@ -126,7 +126,7 @@ module Samagotchi
|
|
|
126
126
|
@clock = clock || -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
|
|
127
127
|
@adapters = {}
|
|
128
128
|
@host_lists = {}
|
|
129
|
-
raw = hosts_config ||
|
|
129
|
+
raw = hosts_config || HostConfig.all(env: env)
|
|
130
130
|
@entries = {}
|
|
131
131
|
raw.each do |key, cfg|
|
|
132
132
|
cfg = HostConfig.coerce(key, cfg)
|