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
|
@@ -176,8 +176,8 @@ module Samagotchi
|
|
|
176
176
|
# @return [String] +model_name+
|
|
177
177
|
def self.check_host!(model_name, env: ENV, hosts: nil)
|
|
178
178
|
require_relative "config"
|
|
179
|
-
hosts ||= Samagotchi::
|
|
180
|
-
parsed = Samagotchi::
|
|
179
|
+
hosts ||= Samagotchi::HostConfig.all(env: env)
|
|
180
|
+
parsed = Samagotchi::HostConfig.model_ref(model_name, env: env, hosts: hosts)
|
|
181
181
|
if (other = parsed.host_conflict)
|
|
182
182
|
raise UnknownHost, "alias '#{parsed.alias_name}' names host '#{other}', not '#{parsed.host_name}'; " \
|
|
183
183
|
"use #{parsed.alias_name} or #{other}:#{parsed.alias_name}"
|
|
@@ -234,9 +234,9 @@ module Samagotchi
|
|
|
234
234
|
def self.model_warning(model_name, env: ENV, hosts: nil, lists: nil, relist: nil, now: Time.now.to_i)
|
|
235
235
|
require_relative "config"
|
|
236
236
|
require_relative "model_list_store"
|
|
237
|
-
hosts ||= Samagotchi::
|
|
237
|
+
hosts ||= Samagotchi::HostConfig.all(env: env)
|
|
238
238
|
lists ||= Samagotchi::ModelListStore
|
|
239
|
-
parsed = Samagotchi::
|
|
239
|
+
parsed = Samagotchi::HostConfig.model_ref(model_name, env: env, hosts: hosts)
|
|
240
240
|
host = parsed.host_name
|
|
241
241
|
return nil unless host
|
|
242
242
|
# Declared under hosts.<name>.models: served whatever the host lists.
|
|
@@ -323,7 +323,7 @@ module Samagotchi
|
|
|
323
323
|
prefix = prefix.strip.downcase
|
|
324
324
|
return nil if hosts.keys.any? { |k| k.to_s.downcase == prefix }
|
|
325
325
|
|
|
326
|
-
Samagotchi::
|
|
326
|
+
Samagotchi::HostConfig.disabled_names(env: env).include?(prefix) ? prefix : nil
|
|
327
327
|
end
|
|
328
328
|
|
|
329
329
|
# "box/x" named host box until '/' stopped naming a host: a saved
|
|
@@ -437,7 +437,7 @@ module Samagotchi
|
|
|
437
437
|
def self.resolve(names:, entry:, client:, bare_model:, override: nil, models: nil)
|
|
438
438
|
require_relative "config"
|
|
439
439
|
override ||= Samagotchi::Config.get_with_origin("model.profile")
|
|
440
|
-
models ||= Samagotchi::
|
|
440
|
+
models ||= Samagotchi::ModelSettings.all
|
|
441
441
|
names = Array(names).map { |n| n.to_s.strip }.reject(&:empty?).uniq
|
|
442
442
|
|
|
443
443
|
value, origin = override
|
|
@@ -6,7 +6,7 @@ require_relative "reasoning_history"
|
|
|
6
6
|
|
|
7
7
|
module Samagotchi
|
|
8
8
|
# One config.yml models: entry (per-model settings keyed by model id or
|
|
9
|
-
# alias), parsed and checked;
|
|
9
|
+
# alias), parsed and checked; ModelSettings.all holds them by
|
|
10
10
|
# downcased key. Each field is nil when unset: profile kept as written
|
|
11
11
|
# (downcased; ModelProfile.resolve validates it), vision true/false,
|
|
12
12
|
# sampling a request-field map, thinking a level, reasoning_history a
|
|
@@ -21,6 +21,41 @@ module Samagotchi
|
|
|
21
21
|
super(**members.to_h { |m| [m, nil] }, **fields)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
# The top-level `models:` map: per-model settings keyed by model id or
|
|
25
|
+
# alias (downcased), e.g. `models: {my-alias: {profile: qwen36}}`, each a
|
|
26
|
+
# ModelSettings; an entry that is not a map is skipped.
|
|
27
|
+
# @return [Hash{String => ModelSettings}]
|
|
28
|
+
def self.all(env: ENV, path: ConfigFile.global_path(env: env))
|
|
29
|
+
data = ConfigFile.read_yaml(env: env, path: path)
|
|
30
|
+
raw = data[ConfigFile::MODELS_KEY] if data.is_a?(Hash)
|
|
31
|
+
return {} unless raw.is_a?(Hash)
|
|
32
|
+
|
|
33
|
+
raw.each_with_object({}) do |(k, v), result|
|
|
34
|
+
key = k.to_s.strip.downcase
|
|
35
|
+
settings = parse(key, v)
|
|
36
|
+
result[key] = settings if settings
|
|
37
|
+
end
|
|
38
|
+
rescue StandardError # degrade-ok: Config↔Log: Log resolves its file and level through Config
|
|
39
|
+
{}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# The first +names+ (model as typed, alias-resolved, bare, …) whose
|
|
43
|
+
# models: entry sets +field+ (a ModelSettings member; another name
|
|
44
|
+
# raises ArgumentError): a ModelSetting (key, value), or nil.
|
|
45
|
+
# VisionSupport and SamplingSettings look a model up the same way.
|
|
46
|
+
# @param models [Hash{String => ModelSettings}, nil] .all by default
|
|
47
|
+
# @return [ModelSetting, nil]
|
|
48
|
+
def self.find(names, field, models: nil)
|
|
49
|
+
raise ArgumentError, "unknown models: field #{field.inspect}" unless members.include?(field)
|
|
50
|
+
|
|
51
|
+
models ||= all
|
|
52
|
+
Array(names).map { |name| name.to_s.strip.downcase }.reject(&:empty?).uniq.each do |key|
|
|
53
|
+
value = models[key]&.public_send(field)
|
|
54
|
+
return ModelSetting.new(key: key, value: value) unless value.nil?
|
|
55
|
+
end
|
|
56
|
+
nil
|
|
57
|
+
end
|
|
58
|
+
|
|
24
59
|
# A models: entry as written, or nil when it is not a map (or the key is
|
|
25
60
|
# blank). A bad value warns, naming the entry, and is left unset.
|
|
26
61
|
# @param key [String] the downcased model id or alias
|
|
@@ -45,7 +80,7 @@ module Samagotchi
|
|
|
45
80
|
end
|
|
46
81
|
end
|
|
47
82
|
|
|
48
|
-
# One field
|
|
83
|
+
# One field ModelSettings.find found: the models: key that set it
|
|
49
84
|
# and its value.
|
|
50
85
|
ModelSetting = Data.define(:key, :value)
|
|
51
86
|
end
|
|
@@ -109,8 +109,7 @@ module Samagotchi
|
|
|
109
109
|
def resolve(given)
|
|
110
110
|
@access.resolve(given)
|
|
111
111
|
rescue ArgumentError => e
|
|
112
|
-
|
|
113
|
-
error_line("chi note: #{message}")
|
|
112
|
+
error_line("chi note: #{e.message}")
|
|
114
113
|
nil
|
|
115
114
|
end
|
|
116
115
|
|
|
@@ -40,9 +40,10 @@ module Samagotchi
|
|
|
40
40
|
# Whether this use is on: config.yml, read at each ask.
|
|
41
41
|
def self.on? = Config.get("objectives.system_one").to_s.strip.downcase == MODE
|
|
42
42
|
|
|
43
|
-
# @param client_for [#call] (judgment, session) → a recording client, nil with no host or no saving
|
|
43
|
+
# @param client_for [#call] (judgment, session) → a recording client, nil with no host or no saving;
|
|
44
|
+
# asked only once an active objective is there
|
|
44
45
|
# @param side [SideJudgments]
|
|
45
|
-
# @param state_dir [
|
|
46
|
+
# @param state_dir [String] the sessions folder
|
|
46
47
|
def initialize(client_for:, side:, state_dir:)
|
|
47
48
|
@client_for = client_for
|
|
48
49
|
@side = side
|
|
@@ -82,7 +83,7 @@ module Samagotchi
|
|
|
82
83
|
def ask(judgment, session, message)
|
|
83
84
|
return if session.nil? || session.id.nil? || session.scratch || session.review?
|
|
84
85
|
|
|
85
|
-
items = Objectives.current(session.id, session.messages, state_dir: @state_dir
|
|
86
|
+
items = Objectives.current(session.id, session.messages, state_dir: @state_dir).items
|
|
86
87
|
.select { |item| item.status == Objectives::ACTIVE }.first(MAX_OBJECTIVES)
|
|
87
88
|
return if items.empty?
|
|
88
89
|
|
|
@@ -106,10 +106,10 @@ module Samagotchi
|
|
|
106
106
|
# @return [Grants::Issued]
|
|
107
107
|
# @raise [Grants::Invalid] an archived session, or bad values
|
|
108
108
|
# (Unbindable: an identity without such a public_url)
|
|
109
|
-
# @raise [
|
|
109
|
+
# @raise [Session::NotFound] no such session
|
|
110
110
|
def invite(grants, session_id:, name:, kind: "human", ttl: nil, cap: nil, identity: nil, public_url: nil,
|
|
111
111
|
mode: nil, state_dir: Session.default_state_dir)
|
|
112
|
-
raise
|
|
112
|
+
raise Session::NotFound, session_id unless File.exist?(Session.session_file(session_id, state_dir: state_dir))
|
|
113
113
|
|
|
114
114
|
identity = Grants.clean_identity(identity) unless identity.nil?
|
|
115
115
|
raise Grants::Unbindable if !identity.nil? && public_url&.identity_header.nil?
|
|
@@ -182,7 +182,7 @@ module Samagotchi
|
|
|
182
182
|
def model_key = @host.model_key&.call
|
|
183
183
|
|
|
184
184
|
# The names the session's model is looked up under now, as chi's own
|
|
185
|
-
# per-model config is (models: maps,
|
|
185
|
+
# per-model config is (models: maps, ModelSettings.find): as
|
|
186
186
|
# typed (an alias), the alias's target, the resolved ref ("host:id"),
|
|
187
187
|
# without the host, the bare id the host serves; deduped, first the
|
|
188
188
|
# most specific. Compare them downcased, first hit, as chi does.
|
|
@@ -212,11 +212,8 @@ module Samagotchi
|
|
|
212
212
|
end
|
|
213
213
|
|
|
214
214
|
def resolve(id, state_dir)
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
sid
|
|
219
|
-
rescue Session::AmbiguousId, ArgumentError => e
|
|
215
|
+
Session.resolve!(id.to_s.strip, state_dir: state_dir)
|
|
216
|
+
rescue ArgumentError => e
|
|
220
217
|
raise Error, e.message
|
|
221
218
|
end
|
|
222
219
|
|
|
@@ -136,19 +136,21 @@ module Samagotchi
|
|
|
136
136
|
def [](key) = (public_send(key) if members.include?(key))
|
|
137
137
|
|
|
138
138
|
# Registry#register's keywords for it: its block wrapped as a handler
|
|
139
|
-
# (typed args, the Context), preview, targets,
|
|
139
|
+
# (typed args, the Context), preview, targets (in the entry's form,
|
|
140
|
+
# (call, surface): a plugin's reads the call only), refs (call only).
|
|
140
141
|
def entry_fields(context)
|
|
141
142
|
block = self.block
|
|
142
143
|
parameters = schema[:parameters]
|
|
143
144
|
preview = per_call(self.preview, parameters)
|
|
145
|
+
targets = per_call(self.targets, parameters)
|
|
144
146
|
{
|
|
145
147
|
schema: schema, label: label,
|
|
146
148
|
handler: lambda { |call, _kctx|
|
|
147
149
|
result = block.call(ToolSpec.args_of(call, parameters), context)
|
|
148
150
|
ToolSpec.result_text(result)
|
|
149
151
|
},
|
|
150
|
-
preview: preview && ->(call) { preview.call(call)&.to_s },
|
|
151
|
-
targets:
|
|
152
|
+
preview: preview && ->(call, _surface = nil) { preview.call(call)&.to_s },
|
|
153
|
+
targets: targets && ->(call, _surface = nil) { targets.call(call) },
|
|
152
154
|
refs: per_call(refs, parameters)
|
|
153
155
|
}
|
|
154
156
|
end
|
|
@@ -107,7 +107,7 @@ module Samagotchi
|
|
|
107
107
|
# @return [Overlay]
|
|
108
108
|
def current = @current
|
|
109
109
|
|
|
110
|
-
# Only from
|
|
110
|
+
# Only from WorkerBoot.run_session_loop, in a spawned worker.
|
|
111
111
|
def install!(overlay) = @current = overlay
|
|
112
112
|
|
|
113
113
|
# #current's env for a tool's spawn, less what every tool runs without
|
|
@@ -68,7 +68,7 @@ module Samagotchi
|
|
|
68
68
|
# the default).
|
|
69
69
|
# @param target [HostRegistry::ModelTarget]
|
|
70
70
|
# @param names [Array<String>] the model's lookup names (HostRegistry#lookup_names)
|
|
71
|
-
# @param models [Hash{String => ModelSettings}, nil]
|
|
71
|
+
# @param models [Hash{String => ModelSettings}, nil] ModelSettings.all (specs)
|
|
72
72
|
# @param session [Symbol, nil] the session's own level (Session#reasoning_history)
|
|
73
73
|
# @return [Array(Symbol, String|nil)]
|
|
74
74
|
def resolve(target, names:, models: nil, session: nil)
|
|
@@ -78,11 +78,11 @@ module Samagotchi
|
|
|
78
78
|
return [env, ENV_NAME] if env
|
|
79
79
|
|
|
80
80
|
models ||= begin
|
|
81
|
-
|
|
81
|
+
ModelSettings.all
|
|
82
82
|
rescue StandardError
|
|
83
83
|
{}
|
|
84
84
|
end
|
|
85
|
-
setting =
|
|
85
|
+
setting = ModelSettings.find(names, :reasoning_history, models: models)
|
|
86
86
|
return [setting.value, "models: #{setting.key}"] if setting
|
|
87
87
|
|
|
88
88
|
host = target.entry&.reasoning_history
|
|
@@ -15,7 +15,7 @@ module Samagotchi
|
|
|
15
15
|
# restart writes one: a stop, an idle exit, a crash and a fallback restart
|
|
16
16
|
# (a TERM) drop the reminders as before, and a stop or an archive removes a
|
|
17
17
|
# file left behind (SessionManager.stop_session, .archive_session); a
|
|
18
|
-
# restart a stop landed on starts no successor (.run_session_loop). A file is honoured only by the same
|
|
18
|
+
# restart a stop landed on starts no successor (WorkerBoot.run_session_loop). A file is honoured only by the same
|
|
19
19
|
# session, within WINDOW of its writing (a successor starts in seconds), so
|
|
20
20
|
# one no successor read can't bring reminders back a day later.
|
|
21
21
|
module ReminderCarry
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "token_usage"
|
|
4
|
+
|
|
5
|
+
module Samagotchi
|
|
6
|
+
# A request's re-prefilled tokens, for both loops: of the prompt the last
|
|
7
|
+
# request on the same model through the same client (the host) sent, what
|
|
8
|
+
# the server didn't reuse from its cache this time (that prompt less the
|
|
9
|
+
# cached count), so an LLM context edit's cache break shows, and so does
|
|
10
|
+
# anything else that rewrote the prompt's middle or lost the cache.
|
|
11
|
+
#
|
|
12
|
+
# One per Engine, given to its KernelLoop and its ChatLoop, so the
|
|
13
|
+
# baseline is the last request's whichever loop sent it: a switch to
|
|
14
|
+
# another host and back starts over on the way back. A bare loop (a spec,
|
|
15
|
+
# a bench) builds its own. Read and written on the turn's thread only
|
|
16
|
+
# (each loop's completed generation; the turn-end warm-up's #reset!).
|
|
17
|
+
class ReprefillMeter
|
|
18
|
+
# The last counted request: its model, its client, its prompt tokens.
|
|
19
|
+
Baseline = Data.define(:model, :client, :prompt)
|
|
20
|
+
|
|
21
|
+
# The tokens the server prefilled again for this request (+prompt+ and
|
|
22
|
+
# +cached+, its counts), and this request becomes the baseline. nil
|
|
23
|
+
# under TokenUsage::REPREFILL_MIN_TOKENS (a cache block's rounding), for
|
|
24
|
+
# a server that reports no cached count (or 0: a cold cache isn't told
|
|
25
|
+
# from no cache), the first request, a model switch (another bare model,
|
|
26
|
+
# or the same one on another host's +client+: SessionMetrics counts that
|
|
27
|
+
# prefill as the switch's), and the first request after #reset!.
|
|
28
|
+
# @return [Integer, nil]
|
|
29
|
+
def tokens(model, client:, prompt:, cached:)
|
|
30
|
+
previous = @baseline
|
|
31
|
+
@baseline = Baseline.new(model: model, client: client, prompt: prompt.to_i) if prompt.to_i.positive?
|
|
32
|
+
return nil unless previous && previous.model == model && previous.client.equal?(client) && cached.to_i.positive?
|
|
33
|
+
|
|
34
|
+
tokens = previous.prompt - cached.to_i
|
|
35
|
+
tokens >= TokenUsage::REPREFILL_MIN_TOKENS ? tokens : nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# A turn-end warm-up prefilled the next prompt off the clock, so the
|
|
39
|
+
# next request's cache counts are against it, not the last request:
|
|
40
|
+
# start over (KernelLoop#warmed_up!).
|
|
41
|
+
def reset!
|
|
42
|
+
@baseline = nil
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -64,7 +64,7 @@ module Samagotchi
|
|
|
64
64
|
rescue ModelProfile::MissingModel, Toolkit::Refused => e
|
|
65
65
|
refused("could not start: #{e.message}")
|
|
66
66
|
rescue ArgumentError => e
|
|
67
|
-
refused(e.
|
|
67
|
+
refused(e.message)
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
private
|
|
@@ -13,7 +13,7 @@ module Samagotchi
|
|
|
13
13
|
|
|
14
14
|
# @param target [HostRegistry::ModelTarget]
|
|
15
15
|
# @param names [Array<String>] the model's lookup names (HostRegistry#lookup_names)
|
|
16
|
-
# @param models [Hash{String => ModelSettings}, nil]
|
|
16
|
+
# @param models [Hash{String => ModelSettings}, nil] ModelSettings.all (specs)
|
|
17
17
|
# @return [Hash] frozen, symbol keys; may be empty
|
|
18
18
|
def self.for(target, names:, models: nil)
|
|
19
19
|
resolve(target, names, models).first
|
|
@@ -39,12 +39,12 @@ module Samagotchi
|
|
|
39
39
|
|
|
40
40
|
def self.resolve(target, names, models)
|
|
41
41
|
models ||= begin
|
|
42
|
-
|
|
42
|
+
ModelSettings.all
|
|
43
43
|
rescue StandardError
|
|
44
44
|
{}
|
|
45
45
|
end
|
|
46
46
|
host = target.entry.sampling || EMPTY
|
|
47
|
-
model =
|
|
47
|
+
model = ModelSettings.find(names, :sampling, models: models)
|
|
48
48
|
sources = []
|
|
49
49
|
sources << "hosts.#{target.entry.name}" unless host.empty?
|
|
50
50
|
sources << "models: #{model.key}" if model
|
|
@@ -184,12 +184,12 @@ module Samagotchi
|
|
|
184
184
|
registry = HostRegistry.new(env: env)
|
|
185
185
|
target = registry.resolve(model)
|
|
186
186
|
names = registry.lookup_names(model, target: target)
|
|
187
|
-
models =
|
|
187
|
+
models = ModelSettings.all(env: env)
|
|
188
188
|
window = ContextWindow.setting(target, names: names, models: models)
|
|
189
189
|
return nil unless window
|
|
190
190
|
return [window.tokens, "hosts.#{target.entry.name}"] if window.source == :host_setting
|
|
191
191
|
|
|
192
|
-
[window.tokens, "models: #{
|
|
192
|
+
[window.tokens, "models: #{ModelSettings.find(names, :window_tokens, models: models).key}"]
|
|
193
193
|
end
|
|
194
194
|
end
|
|
195
195
|
|
|
@@ -224,7 +224,7 @@ module Samagotchi
|
|
|
224
224
|
Degrade.call(:config, "model_ref_unresolved", fallback: false) do
|
|
225
225
|
return true if one == other
|
|
226
226
|
|
|
227
|
-
|
|
227
|
+
HostConfig.model_ref(one, env: env).ref == HostConfig.model_ref(other, env: env).ref
|
|
228
228
|
end
|
|
229
229
|
end
|
|
230
230
|
|
|
@@ -305,7 +305,7 @@ module Samagotchi
|
|
|
305
305
|
# it resolves to (an alias applied, ModelRef), as `chi models` names it.
|
|
306
306
|
def model_ref_name(env: ENV)
|
|
307
307
|
name = model_name
|
|
308
|
-
name &&
|
|
308
|
+
name && HostConfig.model_ref(name, env: env).ref
|
|
309
309
|
end
|
|
310
310
|
|
|
311
311
|
# Routed without model discovery, so an unqualified name may land on
|
|
@@ -383,7 +383,7 @@ module Samagotchi
|
|
|
383
383
|
names = registry.lookup_names(model)
|
|
384
384
|
override = Config.resolve_with_origin("model.profile", env: env, cli_overrides: Config.cli_overrides)
|
|
385
385
|
result = ModelProfile.resolve(names: names, entry: entry, client: nil, bare_model: bare, override: override,
|
|
386
|
-
models:
|
|
386
|
+
models: ModelSettings.all(env: env))
|
|
387
387
|
name = result.profile.name
|
|
388
388
|
case result.source
|
|
389
389
|
when :config then "#{name} (config #{result.detail})"
|
|
@@ -401,7 +401,7 @@ module Samagotchi
|
|
|
401
401
|
target = registry.resolve(model)
|
|
402
402
|
session = session_model(env) ? Thinking.session_level(env[SESSION_THINKING_ENV]) : nil
|
|
403
403
|
Thinking.explain(target, names: registry.lookup_names(model, target: target),
|
|
404
|
-
models:
|
|
404
|
+
models: ModelSettings.all(env: env), session: session).label
|
|
405
405
|
rescue StandardError => e
|
|
406
406
|
"(unknown: #{e.message})"
|
|
407
407
|
end
|
|
@@ -417,7 +417,7 @@ module Samagotchi
|
|
|
417
417
|
|
|
418
418
|
session = session_model(env) ? ReasoningHistory.session_level(env[SESSION_REASONING_HISTORY_ENV]) : nil
|
|
419
419
|
level, source = ReasoningHistory.resolve(target, names: registry.lookup_names(model, target: target),
|
|
420
|
-
models:
|
|
420
|
+
models: ModelSettings.all(env: env), session: session)
|
|
421
421
|
ReasoningHistory.label(level, source)
|
|
422
422
|
rescue StandardError => e
|
|
423
423
|
"(unknown: #{e.message})"
|
data/lib/samagotchi/session.rb
CHANGED
|
@@ -119,7 +119,7 @@ module Samagotchi
|
|
|
119
119
|
# Started by the `delegate` tool (a fork has a parent_id too, but isn't
|
|
120
120
|
# one): see #delegate?.
|
|
121
121
|
attr_writer :delegate
|
|
122
|
-
# The session this one continues (
|
|
122
|
+
# The session this one continues (SessionContinue.run): the
|
|
123
123
|
# previous link of a chain, "the next run of the same routine", else
|
|
124
124
|
# nil. Not a parent: archiving one link leaves the next alone. The chain
|
|
125
125
|
# itself is derived (SessionChain), never stored.
|
|
@@ -353,7 +353,7 @@ module Samagotchi
|
|
|
353
353
|
attr_accessor :model_warning
|
|
354
354
|
|
|
355
355
|
# The delegates a continue moved to this new link (ChildMove;
|
|
356
|
-
#
|
|
356
|
+
# SessionContinue.run sets it); not saved.
|
|
357
357
|
attr_accessor :moved_children
|
|
358
358
|
|
|
359
359
|
# The attached context a continue took to this new link
|
|
@@ -361,7 +361,7 @@ module Samagotchi
|
|
|
361
361
|
attr_accessor :moved_context
|
|
362
362
|
|
|
363
363
|
# The text web `continue →` prefills in this new link
|
|
364
|
-
# (SessionChain.opener_for;
|
|
364
|
+
# (SessionChain.opener_for; SessionContinue.run sets it); not
|
|
365
365
|
# saved.
|
|
366
366
|
attr_accessor :opener
|
|
367
367
|
|
|
@@ -452,6 +452,20 @@ module Samagotchi
|
|
|
452
452
|
@preloaded_memory_names.include?(DELEGATE_MEMORY)
|
|
453
453
|
end
|
|
454
454
|
|
|
455
|
+
# The turn state (idle/running/…) the lists show: the saved status,
|
|
456
|
+
# except a "running" with no live +owner+ (an OwnerLock::Owner, or nil):
|
|
457
|
+
# the worker that saved it died mid-turn, so nothing runs. The one rule
|
|
458
|
+
# behind chi web's rows and pages (Web::SessionSummary.displayed_status,
|
|
459
|
+
# which prefers a live worker's snapshot) and `chi sessions list`
|
|
460
|
+
# (SessionManager.session_summaries and the plain table).
|
|
461
|
+
# @param owner [OwnerLock::Owner, nil] the session's owner now
|
|
462
|
+
# @return [String]
|
|
463
|
+
def displayed_status(owner:)
|
|
464
|
+
return status unless status == STATUS_RUNNING
|
|
465
|
+
|
|
466
|
+
owner ? status : STATUS_IDLE
|
|
467
|
+
end
|
|
468
|
+
|
|
455
469
|
# The question this session waits on, as the lists show it: {id:, kind:}
|
|
456
470
|
# with kind "question" (the model's), "approval", "hook" or "continue"
|
|
457
471
|
# (the step-limit question, asked between turns), +relayed_to+ and
|
|
@@ -559,9 +573,12 @@ module Samagotchi
|
|
|
559
573
|
valid_id?(session_id) && File.exist?(session_path(session_id, state_dir: state_dir))
|
|
560
574
|
end
|
|
561
575
|
|
|
576
|
+
# @raise [NotFound] no such session (an id that isn't one too)
|
|
562
577
|
def self.load(session_id, state_dir: default_state_dir)
|
|
578
|
+
raise NotFound, session_id unless valid_id?(session_id)
|
|
579
|
+
|
|
563
580
|
path = session_path(session_id, state_dir: state_dir)
|
|
564
|
-
raise
|
|
581
|
+
raise NotFound, session_id unless File.exist?(path)
|
|
565
582
|
|
|
566
583
|
session = from_h(JSON.parse(File.read(path)))
|
|
567
584
|
session.status = STATUS_STOPPED if stopped_marker?(session.id, state_dir: state_dir)
|
|
@@ -667,8 +684,34 @@ module Samagotchi
|
|
|
667
684
|
end
|
|
668
685
|
end
|
|
669
686
|
|
|
687
|
+
# What every surface says when an id (or a prefix) names no session:
|
|
688
|
+
# this class owns the words, callers add their context around
|
|
689
|
+
# #message. +given+ is what was asked for, as given.
|
|
690
|
+
class NotFound < ArgumentError
|
|
691
|
+
attr_reader :given
|
|
692
|
+
|
|
693
|
+
def initialize(given)
|
|
694
|
+
@given = given.to_s
|
|
695
|
+
super("no session #{@given[0, 80]}")
|
|
696
|
+
end
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
# The full id of an existing session that +given+ (an id or a unique
|
|
700
|
+
# prefix) names: the one lookup the commands, SessionAccess::Local, chi
|
|
701
|
+
# web's routes and the tools share.
|
|
702
|
+
# @raise [NotFound] none (an id that isn't one too)
|
|
703
|
+
# @raise [AmbiguousId] a prefix of several
|
|
704
|
+
def self.resolve!(given, state_dir: default_state_dir)
|
|
705
|
+
raise NotFound, given unless valid_id?(given.to_s)
|
|
706
|
+
|
|
707
|
+
id = resolve_id(given, state_dir: state_dir)
|
|
708
|
+
raise NotFound, given unless exist?(id, state_dir: state_dir)
|
|
709
|
+
|
|
710
|
+
id
|
|
711
|
+
end
|
|
712
|
+
|
|
670
713
|
# A session id or a unique prefix of one (like git's): the full id. An
|
|
671
|
-
# unknown one comes back as is
|
|
714
|
+
# unknown one comes back as is (resolve! raises NotFound for it); one that
|
|
672
715
|
# names several sessions raises AmbiguousId, which lists them.
|
|
673
716
|
def self.resolve_id(id_or_prefix, state_dir: default_state_dir)
|
|
674
717
|
id = id_or_prefix.to_s
|
|
@@ -832,9 +875,9 @@ module Samagotchi
|
|
|
832
875
|
|
|
833
876
|
# Mark a session as stopped (STOPPED_FILE); its session file stays as
|
|
834
877
|
# it is.
|
|
835
|
-
# @raise [
|
|
878
|
+
# @raise [NotFound] when there is no such session
|
|
836
879
|
def self.mark_stopped(session_id, state_dir: default_state_dir)
|
|
837
|
-
raise
|
|
880
|
+
raise NotFound, session_id unless exist?(session_id, state_dir: state_dir)
|
|
838
881
|
|
|
839
882
|
dir = session_dir(session_id, state_dir: state_dir)
|
|
840
883
|
FileUtils.mkdir_p(dir)
|
|
@@ -33,6 +33,8 @@ module Samagotchi
|
|
|
33
33
|
# Loaded on first use: SessionManager and its ~80 files only when a
|
|
34
34
|
# command reaches it (chi context never does).
|
|
35
35
|
autoload :SessionManager, File.expand_path("../session_manager", __dir__)
|
|
36
|
+
# A continue (#create with continues); it requires session_manager.
|
|
37
|
+
autoload :SessionContinue, File.expand_path("../session_continue", __dir__)
|
|
36
38
|
|
|
37
39
|
module SessionAccess
|
|
38
40
|
# This machine's sessions: the calls the session commands made before
|
|
@@ -63,11 +65,11 @@ module Samagotchi
|
|
|
63
65
|
# @raise [Session::AmbiguousId]
|
|
64
66
|
def resolve_id(given) = Session.resolve_id(given, **dir)
|
|
65
67
|
|
|
66
|
-
# The full id of +given+, of a session that exists.
|
|
67
|
-
# @raise [
|
|
68
|
+
# The full id of +given+, of a session that exists (and loads).
|
|
69
|
+
# @raise [Session::NotFound] no such session (Session::AmbiguousId: a
|
|
68
70
|
# prefix of several)
|
|
69
71
|
def resolve(given)
|
|
70
|
-
id =
|
|
72
|
+
id = Session.resolve!(given, **dir)
|
|
71
73
|
show(id)
|
|
72
74
|
id
|
|
73
75
|
end
|
|
@@ -140,7 +142,7 @@ module Samagotchi
|
|
|
140
142
|
|
|
141
143
|
start = spec.preview ? { prompt: spec.prompt, preview: spec.preview } : { prompt: spec.prompt }
|
|
142
144
|
if spec.continues
|
|
143
|
-
return
|
|
145
|
+
return SessionContinue.run(spec.continues, **start, model: spec.model_name, **dir)
|
|
144
146
|
end
|
|
145
147
|
|
|
146
148
|
SessionManager.spawn_session(**start, working_directory: spec.working_directory, model_name: spec.model_name,
|
|
@@ -269,7 +271,7 @@ module Samagotchi
|
|
|
269
271
|
# @return [Boolean] whether the worker released the session
|
|
270
272
|
def stop(id, wait:) = SessionManager.stop_session(id, wait: wait, **dir)
|
|
271
273
|
|
|
272
|
-
# @return [SessionManager::
|
|
274
|
+
# @return [SessionManager::Restarted]
|
|
273
275
|
def restart(id) = SessionManager.restart_session(id, **dir)
|
|
274
276
|
|
|
275
277
|
# @return [SessionManager::ArchiveResult]
|
|
@@ -137,11 +137,11 @@ module Samagotchi
|
|
|
137
137
|
end
|
|
138
138
|
end
|
|
139
139
|
|
|
140
|
-
# @raise [
|
|
140
|
+
# @raise [Session::NotFound] no such session (Session::AmbiguousId: a
|
|
141
141
|
# prefix of several)
|
|
142
142
|
def resolve(given)
|
|
143
143
|
id = resolve_id(given)
|
|
144
|
-
raise
|
|
144
|
+
raise Session::NotFound, given if id == given.to_s && !exists?(id)
|
|
145
145
|
|
|
146
146
|
id
|
|
147
147
|
end
|
|
@@ -704,6 +704,20 @@ module Samagotchi
|
|
|
704
704
|
reply.json["detail"].to_s.start_with?("no installed bundle resolves")
|
|
705
705
|
end
|
|
706
706
|
|
|
707
|
+
# chi web's 404 not_found detail for a session it doesn't have:
|
|
708
|
+
# "no session X" (Session::NotFound), or "Session not found: X" from
|
|
709
|
+
# chi web 0.155.0 and older (compat N-1: the same line either way).
|
|
710
|
+
SESSION_NOT_FOUND = /\A(?:no session |Session not found: )(.+)\z/m
|
|
711
|
+
|
|
712
|
+
# Session::NotFound for a missing session's 404, else an
|
|
713
|
+
# ArgumentError with chi web's words (another route's 404). chi web
|
|
714
|
+
# never answers Session::NONE_YET ("no session yet") as a 404
|
|
715
|
+
# not_found; were it to, the pattern would read it as NotFound("yet").
|
|
716
|
+
def not_found_error(detail)
|
|
717
|
+
given = detail[SESSION_NOT_FOUND, 1]
|
|
718
|
+
given ? Session::NotFound.new(given) : ArgumentError.new(detail)
|
|
719
|
+
end
|
|
720
|
+
|
|
707
721
|
# Raises what Local's callee raises for this refusal (chi web sends
|
|
708
722
|
# the fields each class is built from; see docs/architecture.md, chi
|
|
709
723
|
# web's routes for the session commands), else RemoteFailed.
|
|
@@ -714,7 +728,7 @@ module Samagotchi
|
|
|
714
728
|
sid = json["session_id"]
|
|
715
729
|
reason = json["reason"]&.to_sym
|
|
716
730
|
case error
|
|
717
|
-
when "not_found" then raise
|
|
731
|
+
when "not_found" then raise not_found_error(message.to_s)
|
|
718
732
|
when "ambiguous_id" then raise Session::AmbiguousId.new(message, ids: Array(json["ids"]))
|
|
719
733
|
when "owned_by_tui"
|
|
720
734
|
raise SessionManager::ScratchSession, sid if json["scratch"] == true
|
|
@@ -82,7 +82,7 @@ module Samagotchi
|
|
|
82
82
|
@stdout.puts("#{short} refused: #{e.message}")
|
|
83
83
|
false
|
|
84
84
|
rescue ArgumentError => e
|
|
85
|
-
error_line("chi sessions archive: #{e.
|
|
85
|
+
error_line("chi sessions archive: #{e.message}")
|
|
86
86
|
false
|
|
87
87
|
rescue SystemCallError => e
|
|
88
88
|
@stdout.puts("#{short} failed: #{e.message}")
|
|
@@ -96,7 +96,7 @@ module Samagotchi
|
|
|
96
96
|
@stdout.puts(result[:unarchived].empty? ? "#{short} not archived" : "#{short} unarchived")
|
|
97
97
|
true
|
|
98
98
|
rescue ArgumentError => e
|
|
99
|
-
error_line("chi sessions unarchive: #{e.
|
|
99
|
+
error_line("chi sessions unarchive: #{e.message}")
|
|
100
100
|
false
|
|
101
101
|
rescue SystemCallError => e
|
|
102
102
|
@stdout.puts("#{short} failed: #{e.message}")
|
|
@@ -15,7 +15,7 @@ module Samagotchi
|
|
|
15
15
|
# "the next run of the same routine". The chain is derived, never stored:
|
|
16
16
|
# follow +continues+ back for the earlier links, forward (the session whose
|
|
17
17
|
# +continues+ names this one) for the later ones. A link is continued at
|
|
18
|
-
# most once (
|
|
18
|
+
# most once (SessionContinue.run refuses a second), so the
|
|
19
19
|
# chain's latest link is the one nobody continues.
|
|
20
20
|
module SessionChain
|
|
21
21
|
# `--continues last:<id>`: the latest link of the chain <id> is in.
|
|
@@ -62,15 +62,16 @@ module Samagotchi
|
|
|
62
62
|
# A session to continue as the CLI and the web name it: an id, a unique
|
|
63
63
|
# prefix, or `last:<id or prefix>` for that chain's latest link.
|
|
64
64
|
# @return [String] the full id of an existing session
|
|
65
|
-
# @raise [
|
|
65
|
+
# @raise [Session::NotFound] none such (Session::AmbiguousId for a prefix of several)
|
|
66
66
|
def resolve(ref, state_dir:)
|
|
67
67
|
given = ref.to_s.strip
|
|
68
68
|
last = given.start_with?(LAST_PREFIX)
|
|
69
69
|
given = given.delete_prefix(LAST_PREFIX) if last
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
id = begin
|
|
71
|
+
Session.resolve!(given, state_dir: state_dir)
|
|
72
|
+
rescue Session::NotFound
|
|
73
|
+
raise Session::NotFound, ref # as given, last: and all
|
|
74
|
+
end
|
|
74
75
|
|
|
75
76
|
last ? latest(id, state_dir: state_dir) : id
|
|
76
77
|
end
|