Class: Terret::Loop
- Inherits:
-
Hames::Service
- Object
- Hames::Service
- Terret::Loop
- Defined in:
- lib/terret/loop.rb
Overview
ctx.loop — the default driver. A step is one model request plus the tool calls it makes; a turn is zero or more steps and closes once nothing is owed. The driver is itself a plugin: replace this row in config and every tool, adapter, and UI keeps working.
Constant Summary collapse
- MAX_STEPS =
25
Instance Method Summary collapse
-
#run_turn(agent, input) ⇒ Object
Runs one turn for
input. - #spawn_agent(session_id:, id: "agent-#{session_id}") ⇒ Object
- #start(ctx) ⇒ Object
Instance Method Details
#run_turn(agent, input) ⇒ Object
Runs one turn for input. Returns the turn status symbol.
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/terret/loop.rb', line 47 def run_turn(agent, input) ctx = agent.ctx sessions = ctx[:sessions] sid = agent.session_id agent.status = :running sessions.append(sid, "turn/start", { agent: agent.id }) status = :completed steps = 0 # claim next-step input plus anything waiting in the inbox pending = [input, *agent.drain_inbox].compact loop do claim = ctx.waterfall("agent/pre_step", Claim.of(pending)) { |c| c } if claim.rejected || (steps.zero? && claim..empty? && pending.empty?) # a rejected or empty first claim still closes a durable turn that # spent no step, so the log records the attempt status = claim.rejected ? :rejected : :empty break end steps += 1 raise "runaway turn" if steps > MAX_STEPS sessions.append(sid, "step/start", { n: steps }) claim..each { |t| sessions.append(sid, "user/message", { text: t }) } pending = [] history = sessions.(sid) request = LLM::Request.new(model: nil, system: ctx[:prompt].render(agent:), messages: history, tools: ctx[:tools].schemas) request = ctx.waterfall("agent/request", request) sessions.assert_log_invariant!(sid, request.) = ctx[:llm].stream(ctx, role: :main, request: request) do |ev| case ev when LLM::TextDelta sessions.append(sid, "assistant/chunk", { text: ev.text }) end end sessions.append(sid, "assistant/message", { parts: .parts }) calls = .tool_calls if calls.empty? sessions.append(sid, "step/end", { n: steps }) break # nothing owed end calls.each do |tc| sessions.append(sid, "tool/call", { id: tc.id, name: tc.name, args: tc.args }) result = ctx[:tools].execute( Tools::Call.new(id: tc.id, name: tc.name, args: tc.args, session_id: sid) ) sessions.append(sid, "tool/result", { id: result.id, content: result.content, error: result.error }) end sessions.append(sid, "step/end", { n: steps }) # tools owe another request -> next step end ctx.serial("agent/turn_stopping", agent) sessions.append(sid, "turn/end", { status: status }) agent.status = :idle status end |
#spawn_agent(session_id:, id: "agent-#{session_id}") ⇒ Object
42 43 44 |
# File 'lib/terret/loop.rb', line 42 def spawn_agent(session_id:, id: "agent-#{session_id}") Agent.new(id:, session_id:, ctx: @ctx.fork) end |
#start(ctx) ⇒ Object
38 39 40 |
# File 'lib/terret/loop.rb', line 38 def start(ctx) @ctx = ctx end |