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
|
@@ -46,7 +46,7 @@ module Samagotchi
|
|
|
46
46
|
from_cwd: note[:from_cwd], text: note[:text], context_source: note[:context_source],
|
|
47
47
|
created_at: note[:created_at], objectives: entry }.compact)
|
|
48
48
|
announce_objectives(session) if entry
|
|
49
|
-
|
|
49
|
+
@objectives_stage.note(session, note, recorder: system_one_recorder, state_dir: session_state_dir)
|
|
50
50
|
message
|
|
51
51
|
end
|
|
52
52
|
end
|
|
@@ -76,14 +76,17 @@ module Samagotchi
|
|
|
76
76
|
# register it on the kernel's llm_context_turn): the same array the loop
|
|
77
77
|
# appends to, not a copy, so read it on the turn's thread (the pending
|
|
78
78
|
# input drain, at an iteration boundary: TurnCheckpoint), or from another
|
|
79
|
-
# only as a dup, read only (/objectives). nil when no
|
|
80
|
-
#
|
|
81
|
-
# last turn's
|
|
79
|
+
# only as a dup, read only (/objectives). nil when no turn runs, and
|
|
80
|
+
# while the running turn's loop has yet to register its own
|
|
81
|
+
# (llm_context_turn is never cleared, and still holds the last turn's
|
|
82
|
+
# then: its turn_id tells them apart).
|
|
82
83
|
# @return [Array<Hash>, nil]
|
|
83
84
|
def live_conversation
|
|
84
|
-
|
|
85
|
+
running = turn_context
|
|
86
|
+
return nil unless running && @kernel.respond_to?(:llm_context_turn)
|
|
85
87
|
|
|
86
|
-
@kernel.llm_context_turn
|
|
88
|
+
forget = @kernel.llm_context_turn
|
|
89
|
+
forget&.turn_id == running.id ? forget.conversation : nil
|
|
87
90
|
end
|
|
88
91
|
|
|
89
92
|
# Append messages to the current session's conversation.
|
|
@@ -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
|
|
@@ -2,26 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
module Samagotchi
|
|
4
4
|
class Engine
|
|
5
|
-
# The session's objectives:
|
|
6
|
-
#
|
|
7
|
-
# is over, System 1's audit of them (ObjectivesAudit)
|
|
8
|
-
# @
|
|
9
|
-
#
|
|
5
|
+
# The session's objectives (TurnStages::Objectives, forwarded): the view
|
|
6
|
+
# announced when it changed, the model's proposals kept as cards until
|
|
7
|
+
# the turn is over, System 1's audit of them (ObjectivesAudit); and
|
|
8
|
+
# Check this's side request. Reads @objectives_stage, @side_judgments,
|
|
9
|
+
# @host_registry, @session.
|
|
10
10
|
module ObjectivesPart
|
|
11
|
-
# The session's objectives as the page draws them
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# a rollback.
|
|
15
|
-
#
|
|
11
|
+
# The session's objectives as the page draws them, when that differs
|
|
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.
|
|
16
18
|
def announce_objectives(session = @session)
|
|
17
19
|
return unless session
|
|
18
20
|
|
|
19
21
|
synchronize_events do
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
@objectives_announced = view
|
|
24
|
-
announce({ type: :objectives_updated, session_id: session.id, **view })
|
|
22
|
+
event = @objectives_stage.announce(session, state_dir: session_state_dir, running: live_conversation)
|
|
23
|
+
announce(event) if event
|
|
25
24
|
end
|
|
26
25
|
rescue StandardError => e
|
|
27
26
|
Log.warn(:turn, "objectives_announce_failed", error: e.class.name, msg: e.message)
|
|
@@ -38,79 +37,30 @@ module Samagotchi
|
|
|
38
37
|
end
|
|
39
38
|
|
|
40
39
|
# A change the model proposes (the objectives tool), kept until the turn
|
|
41
|
-
# is over
|
|
42
|
-
# over a restart (CardStore), not a running turn's that says it waits on the
|
|
43
|
-
# user. The same change twice in a turn is one card.
|
|
40
|
+
# is over (TurnStages::Objectives#propose).
|
|
44
41
|
# @return [Boolean] false past Objectives::PROPOSALS_PER_TURN
|
|
45
|
-
def propose_objective(proposal)
|
|
46
|
-
@objective_proposals_mutex.synchronize do
|
|
47
|
-
next true if @objective_proposals.any? { |p| p.line == proposal.line }
|
|
48
|
-
next false if @objective_proposals.size >= Objectives::PROPOSALS_PER_TURN
|
|
49
|
-
|
|
50
|
-
@objective_proposals << proposal
|
|
51
|
-
true
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def show_objective_proposals
|
|
56
|
-
drain_objective_proposals.each do |proposal|
|
|
57
|
-
id = "objectives-#{SecureRandom.hex(4)}"
|
|
58
|
-
show_card(source: Objectives::SOURCE, id: id, title: proposal.title, body: proposal.body,
|
|
59
|
-
actions: [{ label: "Apply", command: proposal.action_line(id) },
|
|
60
|
-
{ label: "Dismiss", command: "/objectives --card #{id} dismiss" }])
|
|
61
|
-
end
|
|
62
|
-
rescue StandardError => e
|
|
63
|
-
Log.warn(:turn, "objectives_proposal_failed", error: e.class.name, msg: e.message)
|
|
64
|
-
end
|
|
65
|
-
private :show_objective_proposals
|
|
66
|
-
|
|
67
|
-
# A turn that did not complete (canceled, failed) takes its proposals
|
|
68
|
-
# off the buffer without showing them: their Apply card would sit after a
|
|
69
|
-
# turn whose `objectives propose` call is not in the history (a rollback
|
|
70
|
-
# after Ctrl-C drops it), and leaving them in the buffer would show them
|
|
71
|
-
# at the end of the next turn.
|
|
72
|
-
def discard_objective_proposals(turn)
|
|
73
|
-
dropped = drain_objective_proposals
|
|
74
|
-
Log.info(:turn, "objectives_proposals_dropped", count: dropped.size, outcome: turn.outcome.to_s) unless dropped.empty?
|
|
75
|
-
end
|
|
76
|
-
private :discard_objective_proposals
|
|
77
|
-
|
|
78
|
-
def drain_objective_proposals
|
|
79
|
-
@objective_proposals_mutex.synchronize { @objective_proposals.slice!(0..) }
|
|
80
|
-
end
|
|
81
|
-
private :drain_objective_proposals
|
|
42
|
+
def propose_objective(proposal) = @objectives_stage.propose(proposal)
|
|
82
43
|
|
|
83
44
|
private
|
|
84
45
|
|
|
85
|
-
#
|
|
86
|
-
|
|
87
|
-
def audit_objectives_incoming(turn)
|
|
88
|
-
return unless turn.prompt.is_a?(String) && turn.origin.human? && OwnerPrompt.turn?(session: turn.session, author: turn.origin.author)
|
|
89
|
-
|
|
90
|
-
objectives_audit.incoming(turn.session, turn.prompt)
|
|
91
|
-
end
|
|
92
|
-
private :audit_objectives_incoming
|
|
46
|
+
# The objectives' stage (TurnStages::Objectives).
|
|
47
|
+
attr_reader :objectives_stage
|
|
93
48
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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)
|
|
98
56
|
end
|
|
99
|
-
private :audit_objectives_outgoing
|
|
100
|
-
|
|
101
|
-
# With saving off nothing would come of an ask, so none is made.
|
|
102
|
-
def objectives_audit
|
|
103
|
-
@objectives_audit ||= ObjectivesAudit.new(
|
|
104
|
-
side: @side_judgments, state_dir: -> { session_state_dir },
|
|
105
|
-
client_for: lambda do |judgment, session|
|
|
106
|
-
next nil unless system_one_runs.on?
|
|
107
57
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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.
|
|
61
|
+
def build_objectives_stage
|
|
62
|
+
TurnStages::Objectives.new(side_judgments: @side_judgments, host_registry: @host_registry)
|
|
112
63
|
end
|
|
113
|
-
private :objectives_audit
|
|
114
64
|
end
|
|
115
65
|
end
|
|
116
66
|
end
|
|
@@ -5,13 +5,16 @@ module Samagotchi
|
|
|
5
5
|
# The session's participants: the running turn's author, a pair turn's
|
|
6
6
|
# stop check, who came or went, the system prompt's participants line and
|
|
7
7
|
# a participant's author fields. Writes @participant_stop,
|
|
8
|
-
# @participants_line; reads @participants, @participants_watch
|
|
9
|
-
#
|
|
8
|
+
# @participants_line; reads @participants, @participants_watch and the
|
|
9
|
+
# turn's TurnContext.
|
|
10
10
|
module ParticipantsPart
|
|
11
11
|
# The running turn's author when a participant wrote it, else nil
|
|
12
|
-
# (ctx.turn_author, the hooks' author
|
|
12
|
+
# (ctx.turn_author, the hooks' author, the judgments' participant:,
|
|
13
|
+
# ObjectivesCheck): one read of the published TurnContext, so any
|
|
14
|
+
# thread sees the running turn's author from its first moment, never
|
|
15
|
+
# the last turn's.
|
|
13
16
|
# @return [String, nil]
|
|
14
|
-
def turn_author =
|
|
17
|
+
def turn_author = turn_context&.author
|
|
15
18
|
|
|
16
19
|
# Whether a participant's pair turn must stop, read before each of its
|
|
17
20
|
# generations (#guard_participant_spend): author ("p:<id>") → why (their
|
|
@@ -20,7 +23,7 @@ module Samagotchi
|
|
|
20
23
|
# nothing is checked mid-turn.
|
|
21
24
|
attr_writer :participant_stop
|
|
22
25
|
|
|
23
|
-
# Before each generation (KernelLoop#generation_guard): a pair turn whose
|
|
26
|
+
# Before each generation (KernelLoop::Services#generation_guard): a pair turn whose
|
|
24
27
|
# participant's link ended, or whose spend (Engine#metrics: their
|
|
25
28
|
# finished turns, this one's finished generations, their side requests)
|
|
26
29
|
# passed their cap, stops on the plugins' stop path (cost-cap's,
|
|
@@ -28,10 +31,11 @@ module Samagotchi
|
|
|
28
31
|
# once it ends, so a turn can pass the cap by its last generation. The
|
|
29
32
|
# turn-start check is the Worker's (Worker#participant_refusal); a
|
|
30
33
|
# review turn only reads, and keeps to it.
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
# @param context [TurnContext] the running turn's (the loop's)
|
|
35
|
+
def guard_participant_spend(context)
|
|
36
|
+
author = context&.author
|
|
33
37
|
return if author.nil? || @participant_stop.nil?
|
|
34
|
-
return unless
|
|
38
|
+
return unless context.participant_mode == ParticipantMode::PAIR
|
|
35
39
|
|
|
36
40
|
reason = @participant_stop.call(author) or return
|
|
37
41
|
hook_stop_turn(reason, PARTICIPANTS_HOOK)
|
|
@@ -77,8 +81,9 @@ module Samagotchi
|
|
|
77
81
|
# A participant's opening message keeps the rules it was delivered under
|
|
78
82
|
# (ParticipantMode), stamped before any request carries it, so its header
|
|
79
83
|
# never changes when the session's mode does.
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
# @param context [TurnContext] the turn's
|
|
85
|
+
def participant_mode_fields(context)
|
|
86
|
+
mode = context.participant? && context.participant_mode
|
|
82
87
|
mode ? { participant_mode: mode.to_s } : {}
|
|
83
88
|
end
|
|
84
89
|
end
|
|
@@ -101,11 +101,13 @@ module Samagotchi
|
|
|
101
101
|
# Apply the staged tool sets, on the turn thread, before the turn's
|
|
102
102
|
# system prompt is built (PluginTasks#apply_staged_tools!). A changed
|
|
103
103
|
# set may change the chi group the search tool's description counts
|
|
104
|
-
# (a plugin tool the toolkit leaves out).
|
|
104
|
+
# (a plugin tool the toolkit leaves out). Publishes the Registry it
|
|
105
|
+
# leaves (#publish_tools!).
|
|
105
106
|
# @return [Boolean] whether the tools changed
|
|
106
107
|
def apply_staged_tools!
|
|
107
108
|
changed = @plugin_tasks.apply_staged_tools!
|
|
108
109
|
@tool_index.refresh! if changed
|
|
110
|
+
publish_tools!
|
|
109
111
|
changed
|
|
110
112
|
end
|
|
111
113
|
public :apply_staged_tools!
|
|
@@ -15,7 +15,7 @@ module Samagotchi
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
# Ask the model's question (ask_user_question): the kernel's
|
|
18
|
-
# question_handler, called on the turn thread with the payload
|
|
18
|
+
# Services#question_handler, called on the turn thread with the payload
|
|
19
19
|
# Tools::AskUserQuestion.validate made (QuestionDesk#request), after
|
|
20
20
|
# the :question_asked hooks (QuestionAsked) gave the step its refs; a
|
|
21
21
|
# --non-interactive run has no one to ask, so they don't fire there.
|
|
@@ -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.
|
|
@@ -42,31 +43,43 @@ module Samagotchi
|
|
|
42
43
|
def side_author(kind)
|
|
43
44
|
# Check this is only ever the owner's, whichever turn runs as it ends.
|
|
44
45
|
return nil if kind.to_s == ObjectivesCheck::SOURCE
|
|
45
|
-
return nil unless turn_running? || kind.to_s == RECAP_SIDE_KIND
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
# One read each of the published contexts (TurnState): any thread.
|
|
48
|
+
running = turn_context
|
|
49
|
+
return running.author if running
|
|
50
|
+
|
|
51
|
+
kind.to_s == RECAP_SIDE_KIND ? last_turn_context&.author : nil
|
|
48
52
|
end
|
|
49
53
|
|
|
50
54
|
private
|
|
51
55
|
|
|
52
|
-
# +client+ (nil for none) saving its asks as +judgment+'s runs
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
# A scratch session's asks aren't saved; a participant's turn is never
|
|
57
|
-
# 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).
|
|
58
60
|
# @param mode [String, nil] the use's mode, for its runs (Recording.wrap)
|
|
59
61
|
def system_one_recording(client, judgment, session, mode: nil)
|
|
60
62
|
return client if client.nil? || session&.scratch
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
SystemOne::Recording.wrap(client, judgment: judgment, runs: system_one_runs, context: context, recorded_mode: mode)
|
|
64
|
-
rescue StandardError => e
|
|
65
|
-
Log.warn(:system_one, "system_one_recording_failed", error: e.class.name, msg: e.message.to_s[0, 200])
|
|
66
|
-
client
|
|
64
|
+
system_one_recorder.wrap(client, judgment, session, mode: mode)
|
|
67
65
|
end
|
|
68
66
|
private :system_one_recording
|
|
69
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
|
+
|
|
70
83
|
# Where this Engine's runs are saved; read each time, as the settings are.
|
|
71
84
|
def system_one_runs = SystemOne::Runs.for_sessions_dir(session_state_dir)
|
|
72
85
|
private :system_one_runs
|
|
@@ -4,9 +4,9 @@ module Samagotchi
|
|
|
4
4
|
class Engine
|
|
5
5
|
# The session's toolkit: the kits and what a turn declares
|
|
6
6
|
# (ToolkitBinding, forwarded), ctx.load_tools and the staged loads a turn
|
|
7
|
-
# appends, and System 1's toolkit suggestion (
|
|
8
|
-
#
|
|
9
|
-
# @
|
|
7
|
+
# appends, and System 1's toolkit suggestion (TurnStages::ToolkitSuggestion,
|
|
8
|
+
# forwarded). Reads @toolkit_binding, @tools, @tool_index, @tool_loads,
|
|
9
|
+
# @toolkit_suggestion, @session.
|
|
10
10
|
module ToolkitPart
|
|
11
11
|
private
|
|
12
12
|
|
|
@@ -17,22 +17,33 @@ module Samagotchi
|
|
|
17
17
|
def toolkit_explained(toolkit = nil) = @toolkit_binding.explained(toolkit)
|
|
18
18
|
def toolkit_summary = @toolkit_binding.summary
|
|
19
19
|
|
|
20
|
+
# The Registry published for readers off the turn thread
|
|
21
|
+
# (ToolkitBinding#publish!): at the end of the Engine's build, after a
|
|
22
|
+
# turn's staged tool sets and its toolkit apply.
|
|
23
|
+
def publish_tools! = @toolkit_binding.publish!
|
|
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
|
+
|
|
20
29
|
# The tools this session registered (Tools::Registry), declared or not.
|
|
21
30
|
attr_reader :tools
|
|
22
31
|
|
|
23
|
-
public :tools, :toolkit_kits, :resolved_toolkit, :toolkit_explained, :toolkit_summary
|
|
32
|
+
public :tools, :published_tools, :toolkit_kits, :resolved_toolkit, :toolkit_explained, :toolkit_summary
|
|
24
33
|
|
|
25
34
|
# A plugin's ctx.load_tools: stage the reachable tools +names+ names
|
|
26
|
-
# (Tools::ToolIndex#deferred_name
|
|
27
|
-
# nor loaded with an earlier tool change, for
|
|
28
|
-
# that names no tool at all is logged; a
|
|
29
|
-
# skipped quietly.
|
|
35
|
+
# (Tools::ToolIndex#deferred_name, on the published Registry) that the
|
|
36
|
+
# session neither declares nor loaded with an earlier tool change, for
|
|
37
|
+
# bundle +bundle+. A name that names no tool at all is logged; a
|
|
38
|
+
# declared or removed one is skipped quietly.
|
|
30
39
|
# @return [Array<String>] the wire names staged
|
|
31
40
|
def load_tools(names, bundle)
|
|
32
41
|
added = ToolLoads.added(@session&.messages)
|
|
42
|
+
published = published_tools
|
|
43
|
+
surface = Tools::ToolSurface.for(published)
|
|
33
44
|
wanted = names.filter_map do |name|
|
|
34
|
-
wire = @tool_index.deferred_name(name)
|
|
35
|
-
Log.info(:plugins, "load_tools_skipped", bundle: bundle, tool: name) unless wire ||
|
|
45
|
+
wire = @tool_index.deferred_name(name, surface)
|
|
46
|
+
Log.info(:plugins, "load_tools_skipped", bundle: bundle, tool: name) unless wire || published.key?(name)
|
|
36
47
|
wire
|
|
37
48
|
end
|
|
38
49
|
@tool_loads.stage(wanted - added, by: bundle)
|
|
@@ -79,191 +90,24 @@ module Samagotchi
|
|
|
79
90
|
def apply_toolkit!(session, on_event = nil) = @toolkit_binding.apply!(session, on_event)
|
|
80
91
|
private :apply_toolkit!
|
|
81
92
|
|
|
82
|
-
#
|
|
83
|
-
#
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
# returns the ask's Pending, which the turn joins before apply_toolkit!
|
|
87
|
-
# (#settle_toolkit_suggestion), with the turn's controller as the
|
|
88
|
-
# request's cancel controller.
|
|
89
|
-
# @return [ToolkitSuggest::Pending, nil] nil unless the turn joins an ask
|
|
90
|
-
def start_toolkit_suggestion(turn)
|
|
91
|
-
mode = Config.get("toolkit.suggest.mode").to_s
|
|
92
|
-
return if mode == "off"
|
|
93
|
-
|
|
94
|
-
joined = mode == "on"
|
|
95
|
-
session = turn.session
|
|
96
|
-
default = Toolkit.of(session.toolkit)
|
|
97
|
-
log = toolkit_suggestion_log
|
|
98
|
-
# The predicate checks the session first; the kits (memory kits are
|
|
99
|
-
# read from disk) and the log (read whole) only when it gets there.
|
|
100
|
-
kits = nil
|
|
101
|
-
candidates = nil
|
|
102
|
-
return unless ToolkitSuggest.eligible?(
|
|
103
|
-
session: session, prompt: turn.prompt, author: turn.origin.author,
|
|
104
|
-
candidate_count: lambda {
|
|
105
|
-
kits = @toolkit_binding.kits
|
|
106
|
-
candidates = ToolkitSuggest.candidates(kits, default_name: default.name)
|
|
107
|
-
candidates.size
|
|
108
|
-
},
|
|
109
|
-
last_suggestion: -> { ToolkitSuggest.last_suggestion(log, session.id) },
|
|
110
|
-
defer_turns: Config.get("toolkit.suggest.defer_turns"),
|
|
111
|
-
defer_tokens: Config.get("toolkit.suggest.defer_tokens")
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
# One ask at a time: an eligible turn while the last one still runs
|
|
115
|
-
# goes unasked, and says so.
|
|
116
|
-
if @side_judgments.in_flight?(:toolkit)
|
|
117
|
-
Log.info(:toolkit, "toolkit_suggest_in_flight_skipped", session_id: session.id)
|
|
118
|
-
return
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
client = system_one_recording(SystemOne::Client.from_config(host_registry: @host_registry, mode: "on", tag: :toolkit),
|
|
122
|
-
ToolkitSuggest::JUDGMENT, session, mode: Config.get("toolkit.suggest.mode").to_s)
|
|
123
|
-
return unless client
|
|
124
|
-
|
|
125
|
-
# The warm-up's only value is the cache: the real ask never queues
|
|
126
|
-
# behind it.
|
|
127
|
-
stop_toolkit_warmup
|
|
93
|
+
# System 1's toolkit suggestion (TurnStages::ToolkitSuggestion):
|
|
94
|
+
# prepare_turn starts, settles and abandons it; the Worker warms it.
|
|
95
|
+
attr_reader :toolkit_suggestion
|
|
96
|
+
private :toolkit_suggestion
|
|
128
97
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
snapshots = candidates.map do |kit|
|
|
133
|
-
ToolkitSuggest::Candidate.new(name: kit.name.dup.freeze, description: kit.description.dup.freeze,
|
|
134
|
-
rules: ToolkitSuggest.rules(Toolkit.resolve_kit(kit.name, kits)))
|
|
135
|
-
end.freeze
|
|
136
|
-
default_snapshot = ToolkitSuggest::Candidate.new(name: default.name.dup.freeze, description: nil,
|
|
137
|
-
rules: ToolkitSuggest.rules(default.resolve(kits)))
|
|
138
|
-
registered = @tools.entries(toolkit: nil).reject { |entry| entry.source == Tools::ToolIndex::SOURCE }
|
|
139
|
-
core_tools, plugin_tools = registered.partition(&:core?).map { |entries| entries.map { |entry| entry.name.dup.freeze }.freeze }
|
|
140
|
-
state = ToolkitSuggest.question(
|
|
141
|
-
default: default, kits: kits, project: File.basename(session.project_root.to_s),
|
|
142
|
-
prompt: turn.prompt
|
|
143
|
-
).last
|
|
144
|
-
snapshot = ToolkitSuggest::Snapshot.new(
|
|
145
|
-
session_id: session.id.dup.freeze, turn_number: ToolkitSuggest.turn_number(session.messages),
|
|
146
|
-
default: default_snapshot, candidates: snapshots, core_tools: core_tools, plugin_tools: plugin_tools,
|
|
147
|
-
state: { project: state[:project].dup.freeze, prompt: state[:prompt].dup.freeze }.freeze
|
|
148
|
-
)
|
|
149
|
-
threshold = Config.get("toolkit.suggest.threshold")
|
|
150
|
-
runs = session.scratch ? nil : system_one_runs
|
|
151
|
-
pending = ToolkitSuggest::Pending.new(log: log, joined: joined,
|
|
152
|
-
acted: runs && ->(judged, did) { runs.acted(judged.fields[:run_id], did) },
|
|
153
|
-
timeout_seconds: SystemOne::Client.configured_timeout_ms / 1000.0)
|
|
154
|
-
# On mode: cut by a Stop while the turn waits (Pending#wait), not
|
|
155
|
-
# by one after it went on; audit's ask is never cut.
|
|
156
|
-
controller = joined ? pending.cancel_controller : nil
|
|
157
|
-
@side_judgments.start(:toolkit, warn: ToolkitSuggest.method(:warn), finish: pending.method(:finish)) do
|
|
158
|
-
ToolkitSuggest.judge(client: client, snapshot: snapshot, threshold: threshold,
|
|
159
|
-
cancel_controller: controller)
|
|
160
|
-
end
|
|
161
|
-
joined ? pending : nil
|
|
162
|
-
rescue StandardError => e
|
|
163
|
-
ToolkitSuggest.warn(e)
|
|
164
|
-
nil
|
|
165
|
-
end
|
|
166
|
-
private :start_toolkit_suggestion
|
|
167
|
-
|
|
168
|
-
# On mode, before apply_toolkit!: wait for the ask (#start_toolkit_
|
|
169
|
-
# suggestion) up to System 1's timeout from its start, or until a Stop,
|
|
170
|
-
# and put the session on a candidate picked with confidence ≥ threshold
|
|
171
|
-
# (ToolkitBinding#suggest: origin suggested, saved, the notice). Any
|
|
172
|
-
# other outcome (the default, below threshold, undecided, no judgment,
|
|
173
|
-
# a timeout, a Stop) leaves the session on its default. Nothing here
|
|
174
|
-
# sends a request; prepare_turn's cancel check ends a stopped turn. A
|
|
175
|
-
# Stop wins over an answer already in (Pending#wait checks it first).
|
|
176
|
-
def settle_toolkit_suggestion(turn, pending)
|
|
177
|
-
return if pending.nil?
|
|
178
|
-
|
|
179
|
-
outcome, judged = pending.wait(turn.controller)
|
|
180
|
-
if outcome == :timeout
|
|
181
|
-
# The session starts on its default without a word otherwise: a
|
|
182
|
-
# slow System 1 host (a local one) is the usual cause.
|
|
183
|
-
timeout_ms = SystemOne::Client.configured_timeout_ms
|
|
184
|
-
Log.warn(:toolkit, "toolkit_suggest_timeout", session_id: turn.session.id, timeout_ms: timeout_ms,
|
|
185
|
-
reason: "System 1 gave no answer within system_one.timeout_ms")
|
|
186
|
-
@toolkit_binding.fell_back(turn.session, "System 1 took over #{timeout_ms} ms")
|
|
187
|
-
return
|
|
188
|
-
end
|
|
189
|
-
unless outcome == :answered
|
|
190
|
-
Log.info(:toolkit, "toolkit_suggest_#{outcome}", session_id: turn.session.id)
|
|
191
|
-
return
|
|
192
|
-
end
|
|
193
|
-
return if judged.nil?
|
|
194
|
-
|
|
195
|
-
pending.record(toolkit_suggestion_outcome(turn.session, judged))
|
|
196
|
-
rescue StandardError => e
|
|
197
|
-
ToolkitSuggest.warn(e)
|
|
198
|
-
pending&.record("failed")
|
|
199
|
-
end
|
|
200
|
-
private :settle_toolkit_suggestion
|
|
201
|
-
|
|
202
|
-
# What the turn does with an answered ask, as the log line's did:
|
|
203
|
-
# suggested (switched), chosen (the toolkit isn't the default any
|
|
204
|
-
# more), unavailable (the kit is gone), defer or default.
|
|
205
|
-
def toolkit_suggestion_outcome(session, judged)
|
|
206
|
-
case judged.would
|
|
207
|
-
when "suggested"
|
|
208
|
-
return "chosen" unless session.toolkit_origin == "default"
|
|
209
|
-
return "unavailable" unless @toolkit_binding.kits.key?(judged.suggestion.kit)
|
|
210
|
-
|
|
211
|
-
switched = @toolkit_binding.suggest(session, judged.suggestion.kit, confidence: judged.suggestion.confidence,
|
|
212
|
-
deferred: ToolkitSuggest.model_reply?(session.messages))
|
|
213
|
-
switched ? "suggested" : "chosen"
|
|
214
|
-
when "defer" then "defer"
|
|
215
|
-
when "no_judgment"
|
|
216
|
-
# System 1 failed (its own warn) or answered nothing usable.
|
|
217
|
-
@toolkit_binding.fell_back(session, "System 1 gave no answer")
|
|
218
|
-
"default"
|
|
219
|
-
else "default"
|
|
220
|
-
end
|
|
221
|
-
end
|
|
222
|
-
private :toolkit_suggestion_outcome
|
|
223
|
-
|
|
224
|
-
# On mode, as an idle worker starts (Worker#start_background): one ask
|
|
225
|
-
# with the real question and a stand-in prompt, on its own thread,
|
|
226
|
-
# never logged and never waited for, so System 1's server has the
|
|
227
|
-
# question's prefix cached for the first prompt. Only for a session a
|
|
228
|
-
# first prompt could be asked for: origin default, no model reply, not
|
|
229
|
-
# a delegate child or a review session, at least two candidates; and
|
|
230
|
-
# not when the worker starts on a first prompt (that ask comes at once).
|
|
231
|
-
def warm_toolkit_suggestion!
|
|
232
|
-
return unless Config.get("toolkit.suggest.mode").to_s == "on"
|
|
233
|
-
|
|
234
|
-
session = @session
|
|
235
|
-
return if session.nil? || session.toolkit_origin != "default" || session.delegate? || session.review?
|
|
236
|
-
return if ToolkitSuggest.model_reply?(session.messages) || !session.last_prompt.to_s.strip.empty?
|
|
237
|
-
|
|
238
|
-
default = Toolkit.of(session.toolkit)
|
|
239
|
-
kits = @toolkit_binding.kits
|
|
240
|
-
return if ToolkitSuggest.candidates(kits, default_name: default.name).size < 2
|
|
241
|
-
|
|
242
|
-
client = SystemOne::Client.from_config(host_registry: @host_registry, mode: "on", tag: :toolkit)
|
|
243
|
-
return unless client
|
|
244
|
-
|
|
245
|
-
project = File.basename(session.project_root.to_s)
|
|
246
|
-
@toolkit_warmup_thread = Thread.new do
|
|
247
|
-
ToolkitSuggest.warm(client: client, default: default, kits: kits, project: project)
|
|
248
|
-
end
|
|
249
|
-
rescue StandardError => e
|
|
250
|
-
ToolkitSuggest.warn(e)
|
|
251
|
-
nil
|
|
252
|
-
end
|
|
98
|
+
# On mode, as an idle worker starts (Worker#start_background): the
|
|
99
|
+
# warm-up ask (TurnStages::ToolkitSuggestion#warm!).
|
|
100
|
+
def warm_toolkit_suggestion! = @toolkit_suggestion.warm!(@session)
|
|
253
101
|
public :warm_toolkit_suggestion!
|
|
254
102
|
|
|
255
|
-
#
|
|
256
|
-
#
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
@
|
|
260
|
-
|
|
261
|
-
private :stop_toolkit_warmup
|
|
262
|
-
|
|
263
|
-
def toolkit_suggestion_log
|
|
264
|
-
SideJudgments.log(ToolkitSuggest.log_path(session_state_dir), tag: :toolkit, event: "toolkit_suggest_log_failed")
|
|
103
|
+
# The stage over this Engine's side asks, binding and tools (built in
|
|
104
|
+
# #build_tools_and_plugins, before the plugins load). Its turn's start
|
|
105
|
+
# takes the recorder (#system_one_recorder) per call.
|
|
106
|
+
def build_toolkit_suggestion
|
|
107
|
+
TurnStages::ToolkitSuggestion.new(side_judgments: @side_judgments, host_registry: @host_registry,
|
|
108
|
+
toolkit_binding: @toolkit_binding, tools: @tools)
|
|
265
109
|
end
|
|
266
|
-
private :
|
|
110
|
+
private :build_toolkit_suggestion
|
|
267
111
|
end
|
|
268
112
|
end
|
|
269
113
|
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
|
-
|
|
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",
|