samagotchi 0.154.1 → 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 +29 -1
- data/bin/chi +1 -1
- data/docs/architecture.md +12 -7
- data/docs/bundles.md +21 -0
- data/docs/cli.md +1 -1
- data/docs/guardrails.md +2 -2
- data/docs/hooks.md +1 -1
- data/docs/internals/anthropic.md +5 -5
- data/docs/internals/attached-context.md +9 -6
- data/docs/internals/background-tasks.md +1 -1
- data/docs/internals/block-renderers.md +8 -1
- data/docs/internals/engine.md +642 -831
- data/docs/internals/inline-widgets.md +136 -0
- data/docs/internals/llm-context-forget.md +8 -3
- data/docs/internals/objectives.md +8 -6
- data/docs/internals/participants.md +5 -5
- data/docs/internals/prompt-caching.md +13 -6
- data/docs/internals/remote-sessions.md +3 -1
- data/docs/internals/web-frontend.md +1 -1
- data/docs/internals/working-set.md +2 -1
- data/docs/plugins.md +2 -0
- data/docs/releasing.md +1 -1
- data/docs/sessions.md +4 -4
- data/docs/system-one.md +3 -2
- data/docs/testing.md +1 -1
- data/lib/samagotchi/block_renderers.rb +5 -0
- data/lib/samagotchi/bootstrap/config_writer.rb +1 -1
- data/lib/samagotchi/broadcast_command.rb +3 -3
- data/lib/samagotchi/bundle_command/check.rb +1 -1
- data/lib/samagotchi/bundle_command/try/sandbox.rb +1 -1
- data/lib/samagotchi/bundles/dev/manifest.yml +3 -2
- data/lib/samagotchi/bundles/inline-widgets/manifest.yml +11 -0
- data/lib/samagotchi/bundles/inline-widgets/plugin.rb +76 -0
- 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 +21 -119
- data/lib/samagotchi/engine/guardrails.rb +4 -2
- data/lib/samagotchi/engine/messages.rb +9 -6
- data/lib/samagotchi/engine/models.rb +5 -2
- data/lib/samagotchi/engine/objectives.rb +30 -80
- data/lib/samagotchi/engine/participants.rb +15 -10
- data/lib/samagotchi/engine/plugins.rb +3 -1
- data/lib/samagotchi/engine/questions.rb +1 -1
- data/lib/samagotchi/engine/side_requests.rb +26 -13
- data/lib/samagotchi/engine/toolkit.rb +35 -191
- data/lib/samagotchi/engine/turn_end.rb +1 -1
- data/lib/samagotchi/engine/turn_helpers.rb +16 -39
- data/lib/samagotchi/engine/verify.rb +7 -143
- data/lib/samagotchi/engine.rb +166 -98
- data/lib/samagotchi/engine_peers.rb +1 -1
- data/lib/samagotchi/event_fanout.rb +18 -12
- data/lib/samagotchi/guardrail_wiring.rb +83 -82
- data/lib/samagotchi/guardrails/gate.rb +41 -40
- data/lib/samagotchi/guardrails/pair_boundary.rb +2 -1
- data/lib/samagotchi/guardrails/review_boundary.rb +2 -1
- data/lib/samagotchi/guardrails/targets.rb +28 -11
- data/lib/samagotchi/guardrails_command.rb +1 -1
- 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/inline_widgets.rb +325 -0
- data/lib/samagotchi/kernel_loop.rb +215 -313
- data/lib/samagotchi/llm/anthropic_wire.rb +2 -2
- data/lib/samagotchi/llm/backend.rb +3 -1
- data/lib/samagotchi/llm/chat_loop.rb +87 -49
- data/lib/samagotchi/llm/native_backend.rb +3 -2
- data/lib/samagotchi/llm_context_apply.rb +54 -1
- data/lib/samagotchi/llm_context_forget.rb +5 -3
- 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_binding.rb +3 -3
- 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/prompt_sections.rb +7 -0
- 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/question_desk.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/revise_anchor.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/system_prompt.rb +37 -30
- data/lib/samagotchi/terminal_ui/exit_lines.rb +1 -1
- data/lib/samagotchi/terminal_ui.rb +2 -6
- data/lib/samagotchi/testing/boundary.rb +7 -0
- data/lib/samagotchi/testing/bundle.rb +7 -5
- data/lib/samagotchi/testing/install.rb +3 -2
- 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 +15 -7
- data/lib/samagotchi/tool_declarations.rb +6 -6
- data/lib/samagotchi/tool_output_cut.rb +17 -0
- data/lib/samagotchi/tool_response.rb +4 -3
- data/lib/samagotchi/tool_runner.rb +96 -102
- data/lib/samagotchi/toolkit_binding.rb +40 -14
- data/lib/samagotchi/toolkit_command.rb +1 -1
- data/lib/samagotchi/tools/deferred_tools.rb +17 -21
- 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 +64 -44
- data/lib/samagotchi/tools/send_note.rb +4 -6
- data/lib/samagotchi/tools/tool_index.rb +150 -102
- data/lib/samagotchi/tools/tool_surface.rb +187 -0
- data/lib/samagotchi/turn_context.rb +68 -0
- data/lib/samagotchi/turn_stages/context_judge.rb +172 -0
- data/lib/samagotchi/turn_stages/objectives.rb +150 -0
- data/lib/samagotchi/turn_stages/toolkit_suggestion.rb +257 -0
- data/lib/samagotchi/turn_stages/verify_anchors.rb +168 -0
- data/lib/samagotchi/turn_stages/warmup.rb +82 -0
- data/lib/samagotchi/turn_state.rb +46 -4
- 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/markdown_renderer.rb +39 -14
- data/lib/samagotchi/web/message_display.rb +1 -1
- data/lib/samagotchi/web/participant_access.rb +2 -2
- data/lib/samagotchi/web/public/app.js +5 -1
- data/lib/samagotchi/web/public/block_host.js +74 -199
- data/lib/samagotchi/web/public/block_renderers.css +2 -0
- data/lib/samagotchi/web/public/block_source.js +7 -4
- data/lib/samagotchi/web/public/block_watch.js +256 -0
- data/lib/samagotchi/web/public/composer_fold.js +3 -0
- data/lib/samagotchi/web/public/index.html +28 -9
- data/lib/samagotchi/web/public/inline_widgets.js +146 -0
- data/lib/samagotchi/web/public/owner_verify.js +1 -1
- data/lib/samagotchi/web/public/participant.js +1 -1
- data/lib/samagotchi/web/public/revise_stack.js +1 -1
- data/lib/samagotchi/web/public/stage_watch.js +6 -2
- data/lib/samagotchi/web/public/text_roots.js +1 -1
- data/lib/samagotchi/web/public/verify_target.js +1 -1
- data/lib/samagotchi/web/public/widget_picks.js +130 -0
- data/lib/samagotchi/web/public/widget_walk.js +230 -0
- data/lib/samagotchi/web/public/widgets.css +70 -0
- 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 +29 -1
|
@@ -142,7 +142,7 @@ module Samagotchi
|
|
|
142
142
|
:cached_sum,
|
|
143
143
|
:cache_write_sum,
|
|
144
144
|
# What the server prefilled again of the previous request's prompt
|
|
145
|
-
# (
|
|
145
|
+
# (ReprefillMeter#tokens; an LLM context edit's cache break).
|
|
146
146
|
:reprefill_sum,
|
|
147
147
|
:reasoning_sum,
|
|
148
148
|
:cost_sum,
|
|
@@ -302,7 +302,7 @@ module Samagotchi
|
|
|
302
302
|
waiting = s.waiting_question(live: !!live)
|
|
303
303
|
status = if waiting&.dig(:relayed_to) then "waiting (in parent #{waiting[:relayed_to]})"
|
|
304
304
|
elsif waiting then "waiting"
|
|
305
|
-
else s.
|
|
305
|
+
else s.displayed_status(owner: Samagotchi::SessionManager.session_owner(s.id, state_dir: state_dir))
|
|
306
306
|
end
|
|
307
307
|
"#{s.id} #{status.ljust(8)} #{ctx.call(s).ljust(8)} #{s.updated_at} #{list_text.call(s)}#{flag}#{child}"
|
|
308
308
|
end
|
|
@@ -101,7 +101,7 @@ module Samagotchi
|
|
|
101
101
|
error_line("#{command_name}: #{e.message}")
|
|
102
102
|
CLI::Exit::FAILED
|
|
103
103
|
rescue ArgumentError => e
|
|
104
|
-
error_line("#{command_name}: #{e.
|
|
104
|
+
error_line("#{command_name}: #{e.message}")
|
|
105
105
|
CLI::Exit::FAILED
|
|
106
106
|
end
|
|
107
107
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "json"
|
|
5
|
+
require "time"
|
|
6
|
+
require_relative "atomic_file"
|
|
7
|
+
require_relative "session"
|
|
8
|
+
|
|
9
|
+
module Samagotchi
|
|
10
|
+
# A continue's "starting" marker on its new link: written before the
|
|
11
|
+
# continue moves delegates there (ChildMove) and spawns the link's worker,
|
|
12
|
+
# removed by that worker once it holds the owner lock (Worker#start). While
|
|
13
|
+
# it is #fresh?, nothing else spawns a worker for the link (SpawnPart's
|
|
14
|
+
# resume_session and wake_for_report): a ring from a moved child must not
|
|
15
|
+
# start a second one. CROSS-VERSION: the file name and its {pid, at} are
|
|
16
|
+
# read and cleared by other chi versions (spec/worker_contract_spec.rb).
|
|
17
|
+
module StartingMarker
|
|
18
|
+
# <new link dir>/starting: {pid, at}, from the move to the worker's spawn.
|
|
19
|
+
FILE = "starting"
|
|
20
|
+
# A starting marker older than this, or whose process is gone, is left
|
|
21
|
+
# over from a continue that died: it holds nothing back.
|
|
22
|
+
STALE = 60.0
|
|
23
|
+
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
# Hold off a worker for the continue's new link (#fresh?) until its
|
|
27
|
+
# own worker holds it: written before the move, removed by that worker
|
|
28
|
+
# once it has the owner lock (Worker#start).
|
|
29
|
+
def mark(session_id, state_dir:)
|
|
30
|
+
dir = Session.session_dir(session_id, state_dir: state_dir)
|
|
31
|
+
FileUtils.mkdir_p(dir)
|
|
32
|
+
AtomicFile.write(File.join(dir, FILE), JSON.generate({ "pid" => Process.pid, "at" => Time.now.iso8601(3) }))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def clear(session_id, state_dir:)
|
|
36
|
+
FileUtils.rm_f(File.join(Session.session_dir(session_id, state_dir: state_dir), FILE))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Whether a continue is moving delegates to +session_id+ right now: its
|
|
40
|
+
# marker is there, fresh, and the process that wrote it runs.
|
|
41
|
+
def fresh?(session_id, state_dir:)
|
|
42
|
+
file = File.join(Session.session_dir(session_id, state_dir: state_dir), FILE)
|
|
43
|
+
return false unless File.exist?(file)
|
|
44
|
+
|
|
45
|
+
data = JSON.parse(File.read(file))
|
|
46
|
+
return false if Time.now - Time.iso8601(data["at"].to_s) > STALE
|
|
47
|
+
|
|
48
|
+
process_alive?(data["pid"].to_i)
|
|
49
|
+
rescue JSON::ParserError, ArgumentError, SystemCallError
|
|
50
|
+
false
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Signal 0 only asks whether the process exists.
|
|
54
|
+
def process_alive?(pid)
|
|
55
|
+
return false unless pid.positive?
|
|
56
|
+
|
|
57
|
+
Process.kill(0, pid)
|
|
58
|
+
true
|
|
59
|
+
rescue Errno::ESRCH
|
|
60
|
+
false
|
|
61
|
+
rescue Errno::EPERM
|
|
62
|
+
true
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -37,18 +37,18 @@ module Samagotchi
|
|
|
37
37
|
# "steer.cut_after"; nil for the default).
|
|
38
38
|
# @param target [HostRegistry::ModelTarget]
|
|
39
39
|
# @param names [Array<String>] the model's lookup names (HostRegistry#lookup_names)
|
|
40
|
-
# @param models [Hash{String => ModelSettings}, nil]
|
|
40
|
+
# @param models [Hash{String => ModelSettings}, nil] ModelSettings.all (specs)
|
|
41
41
|
# @return [Array(Integer, String|nil)]
|
|
42
42
|
def resolve(target, names:, models: nil)
|
|
43
43
|
value, origin = Config.get_with_origin("steer.cut_after")
|
|
44
44
|
return [value.to_i, ENV_KEY] if origin == :env
|
|
45
45
|
|
|
46
46
|
models ||= begin
|
|
47
|
-
|
|
47
|
+
ModelSettings.all
|
|
48
48
|
rescue StandardError
|
|
49
49
|
{}
|
|
50
50
|
end
|
|
51
|
-
setting =
|
|
51
|
+
setting = ModelSettings.find(names, :steer_cut_after, models: models)
|
|
52
52
|
return [setting.value, "models: #{setting.key}"] if setting
|
|
53
53
|
|
|
54
54
|
host = target.entry.steer_cut_after
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../log"
|
|
4
|
+
require_relative "recording"
|
|
5
|
+
require_relative "runs"
|
|
6
|
+
|
|
7
|
+
module Samagotchi
|
|
8
|
+
module SystemOne
|
|
9
|
+
# What a turn stage needs to save its System 1 asks as runs: the
|
|
10
|
+
# Engine's Runs and the session's model as the Engine names it now.
|
|
11
|
+
# Engine#system_one_recorder builds one fresh at each stage call (never
|
|
12
|
+
# kept: an outgoing ask after a before_turn hook's model switch names
|
|
13
|
+
# the new model), on the caller's thread; the stage keeps it only for
|
|
14
|
+
# that call. NONE is what a stage gets when the Engine couldn't build
|
|
15
|
+
# one: no runs, every client as it was.
|
|
16
|
+
#
|
|
17
|
+
# +runs+ [Runs, nil] where the runs go (Runs#on? false: system_one.runs
|
|
18
|
+
# off); nil: NONE
|
|
19
|
+
# +session_model+ [String, nil] Engine#effective_model_name at the call
|
|
20
|
+
Recorder = Data.define(:runs, :session_model) do
|
|
21
|
+
# +client+ (nil for none) saving its asks as +judgment+'s runs. Anything
|
|
22
|
+
# going wrong here leaves the client as it was: a run is never worth a
|
|
23
|
+
# judgment. A scratch session's asks aren't saved; a participant's
|
|
24
|
+
# turn is never judged, so it never comes here.
|
|
25
|
+
# @param mode [String, nil] the use's mode, for its runs (Recording.wrap)
|
|
26
|
+
def wrap(client, judgment, session, mode: nil)
|
|
27
|
+
return client if client.nil? || runs.nil? || session&.scratch
|
|
28
|
+
|
|
29
|
+
context = Recording::Context.of(session, session_model: session && session_model)
|
|
30
|
+
Recording.wrap(client, judgment: judgment, runs: runs, context: context, recorded_mode: mode)
|
|
31
|
+
rescue StandardError => e
|
|
32
|
+
Recorder.failed(e)
|
|
33
|
+
client
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Whether runs are saved (an ask whose only use is its run is made
|
|
37
|
+
# only then: ObjectivesAudit).
|
|
38
|
+
def saving? = !runs.nil? && runs.on?
|
|
39
|
+
|
|
40
|
+
# The one warn for a recording that can't be set up.
|
|
41
|
+
def self.failed(error)
|
|
42
|
+
Samagotchi::Log.warn(:system_one, "system_one_recording_failed", error: error.class.name, msg: error.message.to_s[0, 200])
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# No recording: the plain client, no runs.
|
|
47
|
+
Recorder::NONE = Recorder.new(runs: nil, session_model: nil)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -8,6 +8,7 @@ require_relative "memory_paths"
|
|
|
8
8
|
require_relative "paths"
|
|
9
9
|
require_relative "tool_declarations"
|
|
10
10
|
require_relative "tools/memory"
|
|
11
|
+
require_relative "tools/tool_surface"
|
|
11
12
|
require_relative "muted_memories"
|
|
12
13
|
require_relative "model_notes"
|
|
13
14
|
require_relative "bundle_needs"
|
|
@@ -46,7 +47,8 @@ module Samagotchi
|
|
|
46
47
|
attr_reader :requested_memories
|
|
47
48
|
|
|
48
49
|
# @param profile [#call] → ModelProfile
|
|
49
|
-
# @param tools [#call] → Tools::Registry
|
|
50
|
+
# @param tools [#call] → Tools::Registry, nil (no tools): each build
|
|
51
|
+
# reads it as a Tools::ToolSurface under the build's layers
|
|
50
52
|
# @param session [#call] → Session, nil
|
|
51
53
|
# @param thinking [#call] → Symbol, the effective model's level (Thinking)
|
|
52
54
|
# @param model [#call] → ModelIdentity, nil (the section is left out)
|
|
@@ -91,8 +93,10 @@ module Samagotchi
|
|
|
91
93
|
|
|
92
94
|
# A prompt #build made, with what it read while it did: the memory
|
|
93
95
|
# index sizes (#memory_index), the model notes, the files and the
|
|
94
|
-
# plugins' sections it holds,
|
|
95
|
-
|
|
96
|
+
# plugins' sections it holds, its stable part's length (chat) and the
|
|
97
|
+
# Tools::ToolSurface it declares (the tools, the not-loaded line, the
|
|
98
|
+
# kit's prompt text).
|
|
99
|
+
Built = Data.define(:prompt, :memory_index, :notes, :files, :sections, :stable_length, :surface)
|
|
96
100
|
|
|
97
101
|
# The full prompt: #base wrapped with the index and the rest. Built once
|
|
98
102
|
# per loop and level (until #reset!) so the prompt prefix, and the
|
|
@@ -110,7 +114,7 @@ module Samagotchi
|
|
|
110
114
|
# @param chat [Boolean] for the chat loop
|
|
111
115
|
# @param thinking [Symbol, nil] the level (Thinking); nil: the effective model's
|
|
112
116
|
# @param layers [Array<Symbol>] the turn's LLM context layers: the
|
|
113
|
-
# native prompt declares the tools they add (Tools::
|
|
117
|
+
# native prompt declares the tools they add (Tools::ToolSurface#declared)
|
|
114
118
|
def build(chat: false, thinking: nil, layers: [])
|
|
115
119
|
key = [chat, thinking, layers]
|
|
116
120
|
cache = @cache
|
|
@@ -172,7 +176,8 @@ module Samagotchi
|
|
|
172
176
|
# The base prompt (specs, plugins' declarations), with Gemma's tool
|
|
173
177
|
# declarations at its end as #build has them.
|
|
174
178
|
def base(chat: false, layers: [])
|
|
175
|
-
|
|
179
|
+
surface = tool_surface(layers)
|
|
180
|
+
with_tail_declarations(assist_system_prompt(chat: chat, surface: surface), chat: chat, surface: surface)
|
|
176
181
|
end
|
|
177
182
|
|
|
178
183
|
# Names activated via preloaded --memory entries during prompt
|
|
@@ -192,12 +197,20 @@ module Samagotchi
|
|
|
192
197
|
@section_files = []
|
|
193
198
|
@section_plugins = []
|
|
194
199
|
@measured_stable_length = nil
|
|
195
|
-
|
|
196
|
-
|
|
200
|
+
surface = tool_surface(layers)
|
|
201
|
+
prompt = system_prompt_with_index(assist_system_prompt(chat: chat, surface: surface),
|
|
202
|
+
chat: chat, thinking: thinking, surface: surface)
|
|
197
203
|
Built.new(prompt: prompt, memory_index: @measured_index, notes: @section_notes || [],
|
|
198
|
-
files: @section_files.freeze, sections: @section_plugins.freeze, stable_length: @measured_stable_length
|
|
204
|
+
files: @section_files.freeze, sections: @section_plugins.freeze, stable_length: @measured_stable_length,
|
|
205
|
+
surface: surface)
|
|
199
206
|
end
|
|
200
207
|
|
|
208
|
+
# The session's tools as a build under +layers+ declares them, read
|
|
209
|
+
# from the Registry now (one per build: the declarations, the
|
|
210
|
+
# not-loaded line and the kit's text agree).
|
|
211
|
+
# @return [Tools::ToolSurface]
|
|
212
|
+
def tool_surface(layers) = Tools::ToolSurface.for(@tools_lookup.call, layers: layers)
|
|
213
|
+
|
|
201
214
|
def profile = @profile_lookup.call
|
|
202
215
|
|
|
203
216
|
def turn_thinking = @thinking_lookup.call
|
|
@@ -208,8 +221,8 @@ module Samagotchi
|
|
|
208
221
|
|
|
209
222
|
# ── Tool declarations ──────────────────────────────────────────────────────
|
|
210
223
|
|
|
211
|
-
def tool_declarations(
|
|
212
|
-
schemas = ToolDeclarations.native_schemas(
|
|
224
|
+
def tool_declarations(surface)
|
|
225
|
+
schemas = ToolDeclarations.native_schemas(surface)
|
|
213
226
|
case profile.name
|
|
214
227
|
when "qwen36"
|
|
215
228
|
ToolDeclarations.qwen_declarations(schemas)
|
|
@@ -232,7 +245,7 @@ module Samagotchi
|
|
|
232
245
|
|
|
233
246
|
# @param chat [Boolean] for the chat loop: no tool declarations or call
|
|
234
247
|
# syntax (its tools go as schemas with each request)
|
|
235
|
-
def assist_system_prompt(chat: false
|
|
248
|
+
def assist_system_prompt(surface:, chat: false)
|
|
236
249
|
return chat_system_prompt if chat
|
|
237
250
|
|
|
238
251
|
hint = tool_call_hint
|
|
@@ -241,7 +254,7 @@ module Samagotchi
|
|
|
241
254
|
tools = if gemma_tail_declarations?
|
|
242
255
|
"Your tools are declared at the end of this system prompt."
|
|
243
256
|
else
|
|
244
|
-
"You have access to the following tools:\n\n#{tool_declarations(
|
|
257
|
+
"You have access to the following tools:\n\n#{tool_declarations(surface)}\n"
|
|
245
258
|
end
|
|
246
259
|
|
|
247
260
|
<<~SYS
|
|
@@ -332,7 +345,7 @@ module Samagotchi
|
|
|
332
345
|
# memory write changes them) after all of them; the model, the location
|
|
333
346
|
# and the session close the prompt (Gemma's tool declarations still
|
|
334
347
|
# follow them).
|
|
335
|
-
def system_prompt_with_index(base, chat: false, thinking: nil,
|
|
348
|
+
def system_prompt_with_index(base, chat: false, thinking: nil, surface: tool_surface([]))
|
|
336
349
|
thinking_token = chat ? "" : Thinking.native(thinking || turn_thinking, profile).system_token
|
|
337
350
|
indexes = { project: read_memory_index("project"), system: read_memory_index("system") }
|
|
338
351
|
@measured_index = measure_indexes(indexes)
|
|
@@ -340,15 +353,15 @@ module Samagotchi
|
|
|
340
353
|
"Project memories:\n#{indexes[:project]}",
|
|
341
354
|
"System memories:\n#{indexes[:system]}"
|
|
342
355
|
].join("\n\n")
|
|
343
|
-
stable = [thinking_token + base, not_loaded_line(
|
|
344
|
-
project_specific_description, appended_section, kit_prompt_section, plugin_prompt_section, memory_sections]
|
|
356
|
+
stable = [thinking_token + base, not_loaded_line(surface), rg_guidance, system_identity_section, model_notes_section, explicit_memory_section,
|
|
357
|
+
project_specific_description, appended_section, kit_prompt_section(surface), plugin_prompt_section, memory_sections]
|
|
345
358
|
stable_text = stable.compact.join("\n")
|
|
346
359
|
volatile = [participants_section, current_model, project_location, current_session].compact
|
|
347
|
-
return with_tail_declarations(stable_text, chat: chat,
|
|
360
|
+
return with_tail_declarations(stable_text, chat: chat, surface: surface) if volatile.empty?
|
|
348
361
|
|
|
349
362
|
prompt = "#{stable_text}\n#{volatile.join("\n")}"
|
|
350
363
|
@measured_stable_length = stable_text.length + 1 if chat
|
|
351
|
-
with_tail_declarations(prompt, chat: chat,
|
|
364
|
+
with_tail_declarations(prompt, chat: chat, surface: surface)
|
|
352
365
|
end
|
|
353
366
|
|
|
354
367
|
# The tools the session's toolkit leaves out, named once right after the
|
|
@@ -357,15 +370,12 @@ module Samagotchi
|
|
|
357
370
|
# the prompt is what it was before toolkits. The ones call_tool reaches
|
|
358
371
|
# and the ones the toolkit removes (only the user brings those back)
|
|
359
372
|
# get a sentence each; with call_tool not declared, one list.
|
|
360
|
-
def not_loaded_line(
|
|
361
|
-
|
|
362
|
-
return nil unless registry.respond_to?(:left_out_names)
|
|
363
|
-
|
|
364
|
-
names = registry.left_out_names(layers: layers)
|
|
373
|
+
def not_loaded_line(surface)
|
|
374
|
+
names = surface.left_out.keys
|
|
365
375
|
return nil if names.empty?
|
|
366
376
|
|
|
367
|
-
toolkit = "(toolkit #{
|
|
368
|
-
reachable =
|
|
377
|
+
toolkit = "(toolkit #{surface.toolkit&.name})"
|
|
378
|
+
reachable = surface.index_only.map(&:name)
|
|
369
379
|
if reachable.empty?
|
|
370
380
|
return "Not loaded in this session #{toolkit}: #{names.join(", ")}. Only the user can load one (/toolkit add <name>)."
|
|
371
381
|
end
|
|
@@ -387,10 +397,7 @@ module Samagotchi
|
|
|
387
397
|
# sessions on that kit share it, and a memory write doesn't move it.
|
|
388
398
|
# nil on a kit without one and on full: the prompt is what it was
|
|
389
399
|
# before kit prompts.
|
|
390
|
-
def kit_prompt_section
|
|
391
|
-
registry = @tools_lookup.call
|
|
392
|
-
registry.respond_to?(:toolkit) ? registry.toolkit&.prompt : nil
|
|
393
|
-
end
|
|
400
|
+
def kit_prompt_section(surface) = surface.toolkit&.prompt
|
|
394
401
|
|
|
395
402
|
# The plugins' prompt sections (chi.prompt_section), right after the
|
|
396
403
|
# kit prompt and before the memory indexes: a plugin's text depends on
|
|
@@ -406,8 +413,8 @@ module Samagotchi
|
|
|
406
413
|
end
|
|
407
414
|
end
|
|
408
415
|
|
|
409
|
-
def with_tail_declarations(prompt, chat:,
|
|
410
|
-
chat || !gemma_tail_declarations? ? prompt : prompt.rstrip + tool_declarations(
|
|
416
|
+
def with_tail_declarations(prompt, chat:, surface:)
|
|
417
|
+
chat || !gemma_tail_declarations? ? prompt : prompt.rstrip + tool_declarations(surface)
|
|
411
418
|
end
|
|
412
419
|
|
|
413
420
|
def gemma_tail_declarations? = profile.name == "gemma4"
|
|
@@ -14,7 +14,7 @@ module Samagotchi
|
|
|
14
14
|
LATE = "worker not answering in time"
|
|
15
15
|
# An exit the worker agreed to in an empty session. Said before it has
|
|
16
16
|
# left: it checks again once it lets go, and a note or prompt arriving
|
|
17
|
-
# first keeps the session (
|
|
17
|
+
# first keeps the session (WorkerBoot.discard_left_session).
|
|
18
18
|
EMPTY_WORDS = "the session is empty: it will be discarded if nothing arrives before its worker stops."
|
|
19
19
|
EMPTY_EXIT = "Detached; #{EMPTY_WORDS}"
|
|
20
20
|
# Why the worker stayed up after /exit, by the reason it gave.
|
|
@@ -44,8 +44,6 @@ module Samagotchi
|
|
|
44
44
|
# Raised when another process (a worker or another chi) owns the session
|
|
45
45
|
# this run was asked to use.
|
|
46
46
|
class SessionBusy < StandardError; end
|
|
47
|
-
# --resume with an id that has no saved session.
|
|
48
|
-
class SessionNotFound < StandardError; end
|
|
49
47
|
|
|
50
48
|
EMPTY_ANSWER_ERROR = "chi: the model gave an empty answer"
|
|
51
49
|
|
|
@@ -100,9 +98,7 @@ module Samagotchi
|
|
|
100
98
|
# between the load and the lock that this run would later save over.
|
|
101
99
|
# An unknown id is refused before the claim, which would leave an
|
|
102
100
|
# empty session dir behind.
|
|
103
|
-
if session_id && !Session.exist?(session_id)
|
|
104
|
-
raise SessionNotFound, "Session not found: #{session_id}"
|
|
105
|
-
end
|
|
101
|
+
raise Session::NotFound, session_id if session_id && !Session.exist?(session_id)
|
|
106
102
|
|
|
107
103
|
claim_session!(session_id) if session_id
|
|
108
104
|
@resume_session = session_id ? Session.load(session_id) : nil
|
|
@@ -434,7 +430,7 @@ module Samagotchi
|
|
|
434
430
|
|
|
435
431
|
# A fresh session reaches the disk before its first turn: a tool in that
|
|
436
432
|
# turn reads it from there (delegate loads its parent to start a child,
|
|
437
|
-
# "
|
|
433
|
+
# "no session …" otherwise).
|
|
438
434
|
def save_before_first_turn(session)
|
|
439
435
|
session.save unless Session.exist?(session.id)
|
|
440
436
|
end
|
|
@@ -94,6 +94,13 @@ module Samagotchi
|
|
|
94
94
|
"Samagotchi::AnswerRefs.new" => "spec: after_turn's refs: helper, as chi's fire site builds it",
|
|
95
95
|
"Samagotchi::Log" => "spec: stubs Log to check what ctx.log recorded: a kit log recorder could replace it"
|
|
96
96
|
},
|
|
97
|
+
"inline-widgets" => {
|
|
98
|
+
'require "samagotchi/inline_widgets"' => "spec: for InlineWidgets",
|
|
99
|
+
"Samagotchi::InlineWidgets.parse" => "spec: each example the sections teach parses strictly, as chi web " \
|
|
100
|
+
"parses a block (the drawing is core's, so is the parse)",
|
|
101
|
+
"Samagotchi::Plugin::PromptSections::MAX" => "spec: the prompt-section limit a section over is dropped at: " \
|
|
102
|
+
"could be a stable constant"
|
|
103
|
+
},
|
|
97
104
|
"loop-guard" => {
|
|
98
105
|
'require "samagotchi/llm/native_tool_normalizer"' => "spec: for NativeToolNormalizer",
|
|
99
106
|
"Samagotchi::LLM::NativeToolNormalizer.normalize" => "spec: replays recorded calls in the shape chi's parser gives",
|
|
@@ -11,6 +11,7 @@ require_relative "../tools/tool_index"
|
|
|
11
11
|
require_relative "../peer_sources"
|
|
12
12
|
require_relative "../toolkit"
|
|
13
13
|
require_relative "../guardrails/gate"
|
|
14
|
+
require_relative "../turn_context"
|
|
14
15
|
require_relative "../memory_bundle/manifest"
|
|
15
16
|
require_relative "../memory_bundle/source"
|
|
16
17
|
require_relative "../session_setup"
|
|
@@ -239,8 +240,9 @@ module Samagotchi
|
|
|
239
240
|
dispatched = nil
|
|
240
241
|
capture = Hooks::Registry.new
|
|
241
242
|
capture.register(:before_tool_call) { |event| dispatched = event.dup }
|
|
242
|
-
gate = Guardrails::Gate.new(
|
|
243
|
-
gate.evaluate(call, iteration: iteration, params: params,
|
|
243
|
+
gate = Guardrails::Gate.new(hooks: capture, tools: tools, context_for: ->(_turn) { context })
|
|
244
|
+
gate.evaluate(call, context: TurnContext.none, iteration: iteration, params: params,
|
|
245
|
+
verdict: verdict || Verdict.new(call: call))
|
|
244
246
|
raise ArgumentError, "guardrails did not dispatch a before_tool_call event for #{call[:name].inspect}" unless dispatched
|
|
245
247
|
|
|
246
248
|
dispatched.slice(:type, :iteration, :call, :params, :blocked, :block_reason, :guardrail, :context, :targets)
|
|
@@ -256,11 +258,11 @@ module Samagotchi
|
|
|
256
258
|
def tool_call(call, params: nil, cwd: nil, context: nil, verdict: nil, iteration: 1)
|
|
257
259
|
context ||= Guardrails::Context.new(cwd: cwd || @recorder[:cwd], session_id: @recorder[:session_id],
|
|
258
260
|
interface: @recorder[:frontend])
|
|
259
|
-
gate = Guardrails::Gate.new(
|
|
260
|
-
|
|
261
|
+
gate = Guardrails::Gate.new(hooks: hooks, tools: tools, context_for: ->(_turn) { context },
|
|
262
|
+
checks_for: ->(_turn) { [] })
|
|
261
263
|
verdict ||= Verdict.new(call: call)
|
|
262
264
|
params ||= "#{call[:name]} #{call[:content] || call[:path]}".strip
|
|
263
|
-
failing { gate.evaluate(call, iteration: iteration, params: params, verdict: verdict) }
|
|
265
|
+
failing { gate.evaluate(call, context: TurnContext.none, iteration: iteration, params: params, verdict: verdict) }
|
|
264
266
|
end
|
|
265
267
|
|
|
266
268
|
# Run a command line ("/name args") as chi does: the first matching
|
|
@@ -31,7 +31,7 @@ module Samagotchi
|
|
|
31
31
|
# What a bundle registered (#registered): commands, tools (names),
|
|
32
32
|
# hooks per event, inits (labels), toolkits, tool sources and peer
|
|
33
33
|
# sources (names).
|
|
34
|
-
Registered = Data.define(:commands, :tools, :hooks, :inits, :toolkits, :tool_sources, :peer_sources)
|
|
34
|
+
Registered = Data.define(:commands, :tools, :hooks, :inits, :toolkits, :tool_sources, :peer_sources, :prompt_sections)
|
|
35
35
|
# A command: its mid_turn policy and its selection-bar labels (chi.annotation).
|
|
36
36
|
Command = Data.define(:name, :mid_turn, :labels)
|
|
37
37
|
# A hook on an event: the bundle, the hook (its file, or the plugin's) and its priority.
|
|
@@ -98,7 +98,8 @@ module Samagotchi
|
|
|
98
98
|
inits: inits.map { |init| init[:label] },
|
|
99
99
|
toolkits: Array(registries.toolkits).map(&:name),
|
|
100
100
|
tool_sources: registries.tool_index.sources.map(&:label),
|
|
101
|
-
peer_sources: registries.peer_sources.names
|
|
101
|
+
peer_sources: registries.peer_sources.names,
|
|
102
|
+
prompt_sections: registries.prompt_sections.names(name)
|
|
102
103
|
)
|
|
103
104
|
end
|
|
104
105
|
|
data/lib/samagotchi/thinking.rb
CHANGED
|
@@ -84,7 +84,7 @@ module Samagotchi
|
|
|
84
84
|
# the default).
|
|
85
85
|
# @param target [HostRegistry::ModelTarget]
|
|
86
86
|
# @param names [Array<String>] the model's lookup names (HostRegistry#lookup_names)
|
|
87
|
-
# @param models [Hash{String => ModelSettings}, nil]
|
|
87
|
+
# @param models [Hash{String => ModelSettings}, nil] ModelSettings.all (specs)
|
|
88
88
|
# @param session [Symbol, nil] the session's own level (Session#thinking)
|
|
89
89
|
# @return [Array(Symbol, String|nil)]
|
|
90
90
|
def resolve(target, names:, models: nil, session: nil)
|
|
@@ -94,11 +94,11 @@ module Samagotchi
|
|
|
94
94
|
return [global, origin == :cli ? "--thinking" : "SAMAGOTCHI_THINKING_LEVEL"] if global && %i[cli env].include?(origin)
|
|
95
95
|
|
|
96
96
|
models ||= begin
|
|
97
|
-
|
|
97
|
+
ModelSettings.all
|
|
98
98
|
rescue StandardError
|
|
99
99
|
{}
|
|
100
100
|
end
|
|
101
|
-
setting =
|
|
101
|
+
setting = ModelSettings.find(names, :thinking, models: models)
|
|
102
102
|
return [setting.value, "models: #{setting.key}"] if setting
|
|
103
103
|
|
|
104
104
|
host = target.entry.thinking
|
|
@@ -23,7 +23,7 @@ module Samagotchi
|
|
|
23
23
|
class TokenUsage < Data.define(:prompt_tokens, :completion_tokens, :cached_tokens, :cache_write_tokens,
|
|
24
24
|
:reasoning_tokens, :cost, :predicted_per_second, :prompt_per_second, :predicted_ms, :source)
|
|
25
25
|
CHARS_PER_TOKEN = 4.0
|
|
26
|
-
# Re-prefilled tokens under this are none (
|
|
26
|
+
# Re-prefilled tokens under this are none (ReprefillMeter#tokens,
|
|
27
27
|
# SessionMetrics): servers count the cache in blocks (OpenAI's 128).
|
|
28
28
|
REPREFILL_MIN_TOKENS = 128
|
|
29
29
|
|
|
@@ -5,6 +5,7 @@ require_relative "log"
|
|
|
5
5
|
require_relative "command_steps"
|
|
6
6
|
require_relative "tool_view"
|
|
7
7
|
require_relative "tools/execute"
|
|
8
|
+
require_relative "tools/registry"
|
|
8
9
|
require_relative "tools/read"
|
|
9
10
|
require_relative "tools/write"
|
|
10
11
|
require_relative "tools/memory"
|
|
@@ -58,11 +59,11 @@ module Samagotchi
|
|
|
58
59
|
# @param registry [Tools::Registry, nil] the session's tools
|
|
59
60
|
# @param cwd [String, nil] what a file tool's title is relative to (the
|
|
60
61
|
# worker runs in its session's working directory)
|
|
61
|
-
def tool_activity_event(tool_name, call, result, registry: nil, cwd: Dir.pwd)
|
|
62
|
+
def tool_activity_event(tool_name, call, result, registry: nil, cwd: Dir.pwd, surface: nil)
|
|
62
63
|
event = {
|
|
63
64
|
action: tool_activity_action(tool_name, registry: registry, call: call),
|
|
64
65
|
tool: tool_name,
|
|
65
|
-
params: tool_activity_params(tool_name, call, registry: registry),
|
|
66
|
+
params: tool_activity_params(tool_name, call, registry: registry, surface: surface),
|
|
66
67
|
status: tool_activity_status(result, tool_name, command: call.is_a?(Hash) ? call[:content] : nil)
|
|
67
68
|
}
|
|
68
69
|
# A grep that found nothing: ok, and the web row says "no match".
|
|
@@ -359,7 +360,10 @@ module Samagotchi
|
|
|
359
360
|
name == "git" && args.first == "diff" && args.intersect?(%w[--quiet --exit-code])
|
|
360
361
|
end
|
|
361
362
|
|
|
362
|
-
|
|
363
|
+
# +surface+: the turn's Tools::ToolSurface, which a tool's entry is
|
|
364
|
+
# looked up in, for a preview that reads it (call_tool's); nil outside a
|
|
365
|
+
# turn (+registry+'s entry then).
|
|
366
|
+
def tool_activity_params(tool_name, call, registry: nil, surface: nil)
|
|
363
367
|
case tool_name
|
|
364
368
|
when Tools::Execute::NAME
|
|
365
369
|
"command=#{preview_tool_param(call[:content])}"
|
|
@@ -453,7 +457,8 @@ module Samagotchi
|
|
|
453
457
|
parts << "options=#{preview_tool_param(Array(opts).join(","))}" if opts && !Array(opts).empty?
|
|
454
458
|
parts.join(" ")
|
|
455
459
|
else
|
|
456
|
-
registry_params(registry_entry(registry, tool_name), call
|
|
460
|
+
registry_params(surface ? surface.registered[tool_name.to_s] : registry_entry(registry, tool_name), call,
|
|
461
|
+
surface)
|
|
457
462
|
end
|
|
458
463
|
end
|
|
459
464
|
|
|
@@ -471,14 +476,17 @@ module Samagotchi
|
|
|
471
476
|
# key="value" (the parsers' args:, which a registry-less reader such as
|
|
472
477
|
# the web's reload also has); nil for a tool the registry doesn't know
|
|
473
478
|
# that has no args:, and for a built-in without its own words above
|
|
474
|
-
# (the reminder tools).
|
|
475
|
-
|
|
479
|
+
# (the reminder tools). A missing surface (Tools::MissingSurface) is
|
|
480
|
+
# raised, never taken as a failing preview.
|
|
481
|
+
def registry_params(entry, call, surface = nil)
|
|
476
482
|
return nil if entry&.core?
|
|
477
483
|
return nil if entry.nil? && !call[:args].is_a?(Hash)
|
|
478
484
|
|
|
479
485
|
if entry&.preview
|
|
480
486
|
begin
|
|
481
|
-
return entry.
|
|
487
|
+
return entry.preview_of(call, surface)
|
|
488
|
+
rescue Tools::MissingSurface
|
|
489
|
+
raise
|
|
482
490
|
rescue StandardError => e
|
|
483
491
|
Log.warn(:plugins, "plugin_preview_failed", tool: entry.name, error: e.class.name,
|
|
484
492
|
msg: "#{entry.name} preview failed: #{e.message}")
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
4
|
require_relative "tools/args"
|
|
5
|
-
require_relative "
|
|
5
|
+
require_relative "config"
|
|
6
6
|
require_relative "tools/memory"
|
|
7
7
|
|
|
8
8
|
module Samagotchi
|
|
@@ -482,7 +482,7 @@ module Samagotchi
|
|
|
482
482
|
# (Tools::Registry's layer gate), so it is not in TOOL_SCHEMAS, which
|
|
483
483
|
# every turn declares. Its description carries llm_context.policy
|
|
484
484
|
# (#forget_outputs_schema); this one the default's.
|
|
485
|
-
FORGET_OUTPUTS_POLICY =
|
|
485
|
+
FORGET_OUTPUTS_POLICY = Config::DEFAULT_LLM_CONTEXT_POLICY
|
|
486
486
|
FORGET_OUTPUTS_DESCRIPTION = "Forget tool outputs you no longer need, to free context. Every tool output starts " \
|
|
487
487
|
"with an id like [#t42]: list ids to forget, and each output is replaced in place by " \
|
|
488
488
|
"a stub with your note (the call stays). %<policy>s\n" \
|
|
@@ -635,10 +635,10 @@ module Samagotchi
|
|
|
635
635
|
|
|
636
636
|
# The schemas the native (Gemma, Qwen) prompts declare: the built-ins
|
|
637
637
|
# as they are, and each plugin tool's flattened (.flat_schema).
|
|
638
|
-
# @param
|
|
639
|
-
#
|
|
640
|
-
def native_schemas(
|
|
641
|
-
|
|
638
|
+
# @param surface [Tools::ToolSurface] the tools a turn (or a build
|
|
639
|
+
# under its layers) declares (ToolSurface#declared)
|
|
640
|
+
def native_schemas(surface)
|
|
641
|
+
surface.declared.map { |entry| entry.core? ? entry.schema : flat_schema(entry.schema) }
|
|
642
642
|
end
|
|
643
643
|
|
|
644
644
|
# A plugin tool's schema in the shape the built-ins have, which is all
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "config"
|
|
3
4
|
require_relative "tool_output_files"
|
|
4
5
|
|
|
5
6
|
module Samagotchi
|
|
@@ -28,6 +29,22 @@ module Samagotchi
|
|
|
28
29
|
# task's log, the child's reply in its output/ folder), which the tool
|
|
29
30
|
# gave with its text (Tools::RefText) and the gap line names.
|
|
30
31
|
module ToolOutputCut
|
|
32
|
+
# The per-output cap with nothing set (max_tool_output_chars).
|
|
33
|
+
DEFAULT_MAX_CHARS = 10_000
|
|
34
|
+
|
|
35
|
+
# The per-output cap both loops cut tool outputs to (and the emitted
|
|
36
|
+
# tool call events carry): an explicit +override+ wins, then
|
|
37
|
+
# max_tool_output_chars (Config). A non-positive value falls back to
|
|
38
|
+
# DEFAULT_MAX_CHARS (there is intentionally no "unlimited": live UIs get
|
|
39
|
+
# a bounded `output:` plus a truthful `output_truncated:` flag). The one
|
|
40
|
+
# exception is memory_read, which ToolRunner leaves out of this cap
|
|
41
|
+
# (memory.read_max_chars, 0: no fixed cap; a memory is read whole).
|
|
42
|
+
# @return [Integer]
|
|
43
|
+
def self.max_chars(override)
|
|
44
|
+
parsed = (override || Config.get("max_tool_output_chars")).to_i
|
|
45
|
+
parsed.positive? ? parsed : DEFAULT_MAX_CHARS
|
|
46
|
+
end
|
|
47
|
+
|
|
31
48
|
POLICIES = { "execute" => :head_tail_file, "task_wait" => :head_tail_ref, "delegate" => :head_tail_ref,
|
|
32
49
|
"delegate_result" => :head_tail_ref }.freeze
|
|
33
50
|
|
|
@@ -34,14 +34,15 @@ module Samagotchi
|
|
|
34
34
|
# own go to +on_stream_event+. An Interrupt (Ctrl-C) mid-batch yields an
|
|
35
35
|
# #interrupted run for every call left (the one it cut short too), so
|
|
36
36
|
# the loop's conversation answers each call, then goes on up: the turn
|
|
37
|
-
# keeps its progress (Engine#ctrl_c_messages).
|
|
37
|
+
# keeps its progress (Engine#ctrl_c_messages). +context+: the calls'
|
|
38
|
+
# turn (TurnContext), each run's (ToolRunner#run).
|
|
38
39
|
# @return [Array<ToolRun>] the runs
|
|
39
|
-
def run_batch(runner, calls, iteration:, emit:, on_stream_event:, cap:)
|
|
40
|
+
def run_batch(runner, calls, context:, iteration:, emit:, on_stream_event:, cap:)
|
|
40
41
|
# Before anything can be interrupted: the rescue counts it.
|
|
41
42
|
runs = []
|
|
42
43
|
emit.call({ type: :tool_dispatch_started, iteration: iteration, call_count: calls.length })
|
|
43
44
|
calls.each_with_index do |call, index|
|
|
44
|
-
runs << runner.run(call, iteration: iteration, call_index: index + 1, call_count: calls.length,
|
|
45
|
+
runs << runner.run(call, context: context, iteration: iteration, call_index: index + 1, call_count: calls.length,
|
|
45
46
|
on_stream_event: on_stream_event, max_tool_output_chars: cap)
|
|
46
47
|
yield runs.last, index if block_given?
|
|
47
48
|
end
|