terret-core 0.1.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 +7 -0
- data/lib/terret/llm.rb +78 -0
- data/lib/terret/loop.rb +139 -0
- data/lib/terret/sessions.rb +106 -0
- data/lib/terret/tools.rb +58 -0
- data/lib/terret.rb +48 -0
- metadata +66 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: fb6dbfe91797674f464068e08da10eb0d19289dc30d3a791c5c46d9450c9ff13
|
|
4
|
+
data.tar.gz: fa2cabe0f12d5fe1a94d9d14cb22686f9e6208dfd9b5a69316124ee1bf162ee5
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c4cfee58e20514fdeb1e8f3d60e3336916c6c54e169377f1ff64ec1ba1d6373b6dc2a2a01c7f579ede66d0f49a92fc90151947910b9a4bfe5e895dca3ab4b426
|
|
7
|
+
data.tar.gz: c250f6994e87dc668bd6cc60d9e967ff8f1232b972cb0be4853c4c0ba9a257012450424701d93fc13b39106e2dbcb63d3cddf55abccfa5a4ec2ed9bd31a8a2ba
|
data/lib/terret/llm.rb
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Terret
|
|
4
|
+
module LLM
|
|
5
|
+
# Provider-neutral vocabulary. Adapters translate these to wire formats.
|
|
6
|
+
Text = Data.define(:text)
|
|
7
|
+
ToolCall = Data.define(:id, :name, :args)
|
|
8
|
+
ToolResult = Data.define(:id, :content, :error)
|
|
9
|
+
Message = Data.define(:role, :parts) do
|
|
10
|
+
def tool_calls = parts.grep(ToolCall)
|
|
11
|
+
def text = parts.grep(Text).map(&:text).join
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
Request = Data.define(:model, :system, :messages, :tools)
|
|
15
|
+
|
|
16
|
+
# Stream events yielded by adapters.
|
|
17
|
+
TextDelta = Data.define(:text)
|
|
18
|
+
ToolCallEnd = Data.define(:tool_call)
|
|
19
|
+
MessageStop = Data.define(:stop_reason) # :end_turn | :tool_use
|
|
20
|
+
|
|
21
|
+
# ctx.llm — the adapter seam. `llm/stream` is a waterfall wrapping every
|
|
22
|
+
# request: middleware may rewrite the request or replace the stream.
|
|
23
|
+
class Service < Hames::Service
|
|
24
|
+
service_key :llm
|
|
25
|
+
|
|
26
|
+
def start(_ctx)
|
|
27
|
+
@adapters = {}
|
|
28
|
+
@roles = config[:roles] || {} # :main => "fake/scripted"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def register_adapter(name, adapter)
|
|
32
|
+
@adapters[name.to_s] = adapter
|
|
33
|
+
-> { @adapters.delete(name.to_s) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def resolve(role)
|
|
37
|
+
spec = @roles.fetch(role) { raise KeyError, "no model role #{role.inspect}" }
|
|
38
|
+
provider, model = spec.split("/", 2)
|
|
39
|
+
[@adapters.fetch(provider), model]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Streams the request through the `llm/stream` waterfall; the base of
|
|
43
|
+
# the waterfall invokes the resolved adapter. Yields StreamEvents,
|
|
44
|
+
# returns the final assistant Message.
|
|
45
|
+
def stream(ctx, role:, request:, &on_event)
|
|
46
|
+
adapter, model = resolve(role)
|
|
47
|
+
req = request.with(model: model)
|
|
48
|
+
ctx.waterfall("llm/stream", req) do |final_req|
|
|
49
|
+
adapter.stream(final_req, &on_event)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Deterministic scripted adapter for tests, demos, and record/replay.
|
|
55
|
+
# Script: array of hashes { text:, tool_calls: [ToolCall, ...] }.
|
|
56
|
+
class FakeAdapter
|
|
57
|
+
def initialize(script)
|
|
58
|
+
@script = script.dup
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def stream(_request)
|
|
62
|
+
step = @script.shift or raise "FakeAdapter script exhausted"
|
|
63
|
+
parts = []
|
|
64
|
+
if (t = step[:text])
|
|
65
|
+
t.chars.each_slice(8) { |chunk| yield TextDelta.new(text: chunk.join) }
|
|
66
|
+
parts << Text.new(text: t)
|
|
67
|
+
end
|
|
68
|
+
calls = Array(step[:tool_calls])
|
|
69
|
+
calls.each do |tc|
|
|
70
|
+
yield ToolCallEnd.new(tool_call: tc)
|
|
71
|
+
parts << tc
|
|
72
|
+
end
|
|
73
|
+
yield MessageStop.new(stop_reason: calls.empty? ? :end_turn : :tool_use)
|
|
74
|
+
Message.new(role: :assistant, parts: parts)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
data/lib/terret/loop.rb
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Terret
|
|
4
|
+
Claim = Data.define(:messages, :rejected, :reason) do
|
|
5
|
+
def self.of(messages) = new(messages:, rejected: false, reason: nil)
|
|
6
|
+
def self.reject(reason:) = new(messages: [], rejected: true, reason:)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class Agent
|
|
10
|
+
attr_reader :id, :session_id, :ctx
|
|
11
|
+
attr_accessor :status
|
|
12
|
+
|
|
13
|
+
def initialize(id:, session_id:, ctx:)
|
|
14
|
+
@id = id
|
|
15
|
+
@session_id = session_id
|
|
16
|
+
@ctx = ctx # a forked, agent-scoped context
|
|
17
|
+
@inbox = [] # injected context waits here until a waking message
|
|
18
|
+
@status = :idle
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def inject(text)
|
|
22
|
+
@inbox << text
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def drain_inbox = @inbox.slice!(0..)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# ctx.loop — the default driver. A step is one model request plus the tool
|
|
29
|
+
# calls it makes; a turn is zero or more steps and closes once nothing is
|
|
30
|
+
# owed. The driver is itself a plugin: replace this row in config and every
|
|
31
|
+
# tool, adapter, and UI keeps working.
|
|
32
|
+
class Loop < Hames::Service
|
|
33
|
+
service_key :loop
|
|
34
|
+
inject :sessions, :tools, :llm, :prompt
|
|
35
|
+
|
|
36
|
+
MAX_STEPS = 25
|
|
37
|
+
|
|
38
|
+
def start(ctx)
|
|
39
|
+
@ctx = ctx
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def spawn_agent(session_id:, id: "agent-#{session_id}")
|
|
43
|
+
Agent.new(id:, session_id:, ctx: @ctx.fork)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Runs one turn for `input`. Returns the turn status symbol.
|
|
47
|
+
def run_turn(agent, input)
|
|
48
|
+
ctx = agent.ctx
|
|
49
|
+
sessions = ctx[:sessions]
|
|
50
|
+
sid = agent.session_id
|
|
51
|
+
agent.status = :running
|
|
52
|
+
|
|
53
|
+
sessions.append(sid, "turn/start", { agent: agent.id })
|
|
54
|
+
status = :completed
|
|
55
|
+
steps = 0
|
|
56
|
+
|
|
57
|
+
# claim next-step input plus anything waiting in the inbox
|
|
58
|
+
pending = [input, *agent.drain_inbox].compact
|
|
59
|
+
|
|
60
|
+
loop do
|
|
61
|
+
claim = ctx.waterfall("agent/pre_step", Claim.of(pending)) { |c| c }
|
|
62
|
+
if claim.rejected || (steps.zero? && claim.messages.empty? && pending.empty?)
|
|
63
|
+
# a rejected or empty first claim still closes a durable turn that
|
|
64
|
+
# spent no step, so the log records the attempt
|
|
65
|
+
status = claim.rejected ? :rejected : :empty
|
|
66
|
+
break
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
steps += 1
|
|
70
|
+
raise "runaway turn" if steps > MAX_STEPS
|
|
71
|
+
|
|
72
|
+
sessions.append(sid, "step/start", { n: steps })
|
|
73
|
+
claim.messages.each { |t| sessions.append(sid, "user/message", { text: t }) }
|
|
74
|
+
pending = []
|
|
75
|
+
|
|
76
|
+
history = sessions.derive_messages(sid)
|
|
77
|
+
request = LLM::Request.new(model: nil, system: ctx[:prompt].render(agent:),
|
|
78
|
+
messages: history, tools: ctx[:tools].schemas)
|
|
79
|
+
request = ctx.waterfall("agent/request", request)
|
|
80
|
+
sessions.assert_log_invariant!(sid, request.messages)
|
|
81
|
+
|
|
82
|
+
message = ctx[:llm].stream(ctx, role: :main, request: request) do |ev|
|
|
83
|
+
case ev
|
|
84
|
+
when LLM::TextDelta
|
|
85
|
+
sessions.append(sid, "assistant/chunk", { text: ev.text })
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
sessions.append(sid, "assistant/message", { parts: message.parts })
|
|
89
|
+
|
|
90
|
+
calls = message.tool_calls
|
|
91
|
+
if calls.empty?
|
|
92
|
+
sessions.append(sid, "step/end", { n: steps })
|
|
93
|
+
break # nothing owed
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
calls.each do |tc|
|
|
97
|
+
sessions.append(sid, "tool/call", { id: tc.id, name: tc.name, args: tc.args })
|
|
98
|
+
result = ctx[:tools].execute(
|
|
99
|
+
Tools::Call.new(id: tc.id, name: tc.name, args: tc.args, session_id: sid)
|
|
100
|
+
)
|
|
101
|
+
sessions.append(sid, "tool/result",
|
|
102
|
+
{ id: result.id, content: result.content, error: result.error })
|
|
103
|
+
end
|
|
104
|
+
sessions.append(sid, "step/end", { n: steps })
|
|
105
|
+
# tools owe another request -> next step
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
ctx.serial("agent/turn_stopping", agent)
|
|
109
|
+
sessions.append(sid, "turn/end", { status: status })
|
|
110
|
+
agent.status = :idle
|
|
111
|
+
status
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# ctx.prompt — minimal prompt assembly: priority-ordered sections rendered
|
|
116
|
+
# per step; registration is an effect.
|
|
117
|
+
class Prompt < Hames::Service
|
|
118
|
+
service_key :prompt
|
|
119
|
+
|
|
120
|
+
def start(ctx)
|
|
121
|
+
@ctx = ctx
|
|
122
|
+
@sections = []
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def register_section(name, priority: 100, &block)
|
|
126
|
+
entry = [priority, name.to_s, block]
|
|
127
|
+
@ctx.effect do
|
|
128
|
+
@sections << entry
|
|
129
|
+
-> { @sections.delete(entry) }
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def render(env = {})
|
|
134
|
+
@sections.sort_by { |(p, n, _)| [p, n] }
|
|
135
|
+
.filter_map { |(_, _, b)| b.call(env) }
|
|
136
|
+
.join("\n\n")
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require "digest"
|
|
5
|
+
require "json"
|
|
6
|
+
|
|
7
|
+
module Terret
|
|
8
|
+
SessionEvent = Data.define(:id, :session_id, :seq, :at, :type, :payload)
|
|
9
|
+
|
|
10
|
+
class LogInvariantViolation < StandardError; end
|
|
11
|
+
|
|
12
|
+
# ctx.sessions — the append-only session log. The single source of the
|
|
13
|
+
# context the model sees: derive_messages projects model history from it,
|
|
14
|
+
# and a digest invariant asserts that what goes out to an adapter is
|
|
15
|
+
# exactly what replaying the log yields ("model-visible means logged").
|
|
16
|
+
class Sessions < Hames::Service
|
|
17
|
+
service_key :sessions
|
|
18
|
+
|
|
19
|
+
Session = Struct.new(:id, :events, :parent_id, keyword_init: true)
|
|
20
|
+
|
|
21
|
+
def start(ctx)
|
|
22
|
+
@ctx = ctx
|
|
23
|
+
@store = {}
|
|
24
|
+
@dir = config[:jsonl_dir] # optional persistence
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def create(id: SecureRandom.hex(6), parent_id: nil)
|
|
28
|
+
s = Session.new(id:, events: [], parent_id:)
|
|
29
|
+
@store[id] = s
|
|
30
|
+
append(id, "session/created", { parent_id: })
|
|
31
|
+
s
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def fetch(id) = @store.fetch(id)
|
|
35
|
+
|
|
36
|
+
def append(session_id, type, payload = {})
|
|
37
|
+
decl = Hames.event(type)
|
|
38
|
+
raise Hames::ContractError, "#{type} is not a durable event" unless decl.durable
|
|
39
|
+
|
|
40
|
+
s = fetch(session_id)
|
|
41
|
+
ev = SessionEvent.new(
|
|
42
|
+
id: SecureRandom.hex(8), session_id:, seq: s.events.length,
|
|
43
|
+
at: Time.now.utc, type: type.to_s, payload: payload
|
|
44
|
+
)
|
|
45
|
+
s.events << ev
|
|
46
|
+
persist(ev)
|
|
47
|
+
@ctx.emit("session/event", ev)
|
|
48
|
+
ev
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Project provider-neutral model history from the durable log.
|
|
52
|
+
def derive_messages(session_id, upto: nil)
|
|
53
|
+
events = fetch(session_id).events
|
|
54
|
+
events = events.take(upto) if upto
|
|
55
|
+
events.filter_map do |ev|
|
|
56
|
+
case ev.type
|
|
57
|
+
when "user/message", "context/injected"
|
|
58
|
+
LLM::Message.new(role: :user, parts: [LLM::Text.new(text: ev.payload[:text])])
|
|
59
|
+
when "assistant/message"
|
|
60
|
+
LLM::Message.new(role: :assistant, parts: ev.payload[:parts])
|
|
61
|
+
when "tool/result"
|
|
62
|
+
LLM::Message.new(role: :tool, parts: [
|
|
63
|
+
LLM::ToolResult.new(id: ev.payload[:id], content: ev.payload[:content],
|
|
64
|
+
error: ev.payload[:error])
|
|
65
|
+
])
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# The enforcement point for "model-visible means logged": the loop calls
|
|
71
|
+
# this with the message list it is about to send; a mismatch against the
|
|
72
|
+
# log projection raises in dev/test.
|
|
73
|
+
def assert_log_invariant!(session_id, outbound_messages)
|
|
74
|
+
derived = derive_messages(session_id)
|
|
75
|
+
return if digest(derived) == digest(outbound_messages)
|
|
76
|
+
|
|
77
|
+
raise LogInvariantViolation,
|
|
78
|
+
"outbound request diverges from session log projection for #{session_id}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def fork(source_id, boundary: nil, child_id: SecureRandom.hex(6))
|
|
82
|
+
src = fetch(source_id)
|
|
83
|
+
child = Session.new(id: child_id, events: [], parent_id: source_id)
|
|
84
|
+
@store[child_id] = child
|
|
85
|
+
events = boundary ? src.events.take(boundary) : src.events.dup
|
|
86
|
+
events.each { |ev| child.events << ev.with(session_id: child_id) }
|
|
87
|
+
append(child_id, "session/forked", { from: source_id, boundary: boundary })
|
|
88
|
+
child
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def digest(messages)
|
|
94
|
+
Digest::SHA256.hexdigest(messages.map(&:inspect).join("\x1e"))
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def persist(ev)
|
|
98
|
+
return unless @dir
|
|
99
|
+
|
|
100
|
+
File.open(File.join(@dir, "#{ev.session_id}.jsonl"), "a") do |f|
|
|
101
|
+
f.puts JSON.generate(type: ev.type, seq: ev.seq, at: ev.at.iso8601,
|
|
102
|
+
payload: ev.payload.inspect)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
data/lib/terret/tools.rb
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Terret
|
|
4
|
+
module Tools
|
|
5
|
+
Definition = Data.define(:name, :description, :params, :handler,
|
|
6
|
+
:mutating, :approval) do
|
|
7
|
+
def schema = { name:, description:, parameters: params }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
Call = Data.define(:id, :name, :args, :session_id)
|
|
11
|
+
Result = Data.define(:id, :content, :error)
|
|
12
|
+
|
|
13
|
+
# ctx.tools — scoped registry + guarded execution pipeline. Registration
|
|
14
|
+
# is an effect (unloading a plugin removes its tools). Execution runs the
|
|
15
|
+
# three-waterfall pipeline: pre_execute (validate / veto / rewrite) ->
|
|
16
|
+
# execute (a provider may replace execution wholesale) -> post_execute
|
|
17
|
+
# (truncate / redact).
|
|
18
|
+
class Registry < Hames::Service
|
|
19
|
+
service_key :tools
|
|
20
|
+
|
|
21
|
+
def start(ctx)
|
|
22
|
+
@ctx = ctx
|
|
23
|
+
@defs = {}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def register(name:, description:, params: {}, mutating: false,
|
|
27
|
+
approval: :never, &handler)
|
|
28
|
+
d = Definition.new(name: name.to_s, description:, params:, handler:,
|
|
29
|
+
mutating:, approval:)
|
|
30
|
+
@ctx.effect do
|
|
31
|
+
@defs[d.name] = d
|
|
32
|
+
-> { @defs.delete(d.name) }
|
|
33
|
+
end
|
|
34
|
+
d
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def schemas = @defs.values.map(&:schema)
|
|
38
|
+
def fetch(name) = @defs.fetch(name.to_s)
|
|
39
|
+
|
|
40
|
+
def execute(call)
|
|
41
|
+
admitted = @ctx.waterfall("tools/pre_execute", call)
|
|
42
|
+
return Result.new(id: call.id, content: nil, error: admitted.reason) if admitted.is_a?(Veto)
|
|
43
|
+
|
|
44
|
+
result = @ctx.waterfall("tools/execute", admitted) do |c|
|
|
45
|
+
d = fetch(c.name)
|
|
46
|
+
begin
|
|
47
|
+
Result.new(id: c.id, content: d.handler.call(**c.args), error: nil)
|
|
48
|
+
rescue => e
|
|
49
|
+
Result.new(id: c.id, content: nil, error: "#{e.class}: #{e.message}")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
@ctx.waterfall("tools/post_execute", result)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
Veto = Data.define(:reason)
|
|
57
|
+
end
|
|
58
|
+
end
|
data/lib/terret.rb
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require "hames"
|
|
5
|
+
rescue LoadError
|
|
6
|
+
require_relative "../../hames/lib/hames" # monorepo path source
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module Terret
|
|
10
|
+
VERSION = "0.1.0"
|
|
11
|
+
|
|
12
|
+
# Event vocabulary. Durable events land in the session log; the rest are
|
|
13
|
+
# live extension points. Modes are the public contract (see docs/events.md).
|
|
14
|
+
def self.declare_events!
|
|
15
|
+
e = ->(name, mode, durable: false, doc: nil) {
|
|
16
|
+
Hames.event(name, mode:, durable:, doc:)
|
|
17
|
+
}
|
|
18
|
+
# durable session events
|
|
19
|
+
e.("session/created", :emit, durable: true, doc: "session opened")
|
|
20
|
+
e.("session/forked", :emit, durable: true, doc: "lineage record")
|
|
21
|
+
e.("turn/start", :emit, durable: true, doc: "turn opened")
|
|
22
|
+
e.("turn/end", :emit, durable: true, doc: "turn closed with status")
|
|
23
|
+
e.("step/start", :emit, durable: true, doc: "model step opened")
|
|
24
|
+
e.("step/end", :emit, durable: true, doc: "model step closed")
|
|
25
|
+
e.("user/message", :emit, durable: true, doc: "entered user input")
|
|
26
|
+
e.("assistant/chunk", :emit, durable: true, doc: "raw stream delta (replay/UI fidelity)")
|
|
27
|
+
e.("assistant/message", :emit, durable: true, doc: "completed assistant message")
|
|
28
|
+
e.("tool/call", :emit, durable: true, doc: "tool invocation requested by model")
|
|
29
|
+
e.("tool/result", :emit, durable: true, doc: "tool outcome")
|
|
30
|
+
e.("context/injected", :emit, durable: true, doc: "agent.inject landed in a request")
|
|
31
|
+
# live extension points
|
|
32
|
+
e.("session/event", :emit, doc: "fan-out of every durable append")
|
|
33
|
+
e.("agent/pre_step", :waterfall, doc: "rewrite or reject the claimed messages")
|
|
34
|
+
e.("agent/request", :waterfall, doc: "rewrite the outbound model request")
|
|
35
|
+
e.("llm/stream", :waterfall, doc: "wrap/replace the adapter stream")
|
|
36
|
+
e.("tools/pre_execute", :waterfall, doc: "validate, veto, or rewrite a call")
|
|
37
|
+
e.("tools/execute", :waterfall, doc: "a provider may replace execution")
|
|
38
|
+
e.("tools/post_execute", :waterfall, doc: "truncate/redact the result")
|
|
39
|
+
e.("agent/turn_stopping", :serial, doc: "ordered veto point before turn/end")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
require_relative "terret/llm"
|
|
44
|
+
require_relative "terret/sessions"
|
|
45
|
+
require_relative "terret/tools"
|
|
46
|
+
require_relative "terret/loop"
|
|
47
|
+
|
|
48
|
+
Terret.declare_events!
|
metadata
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: terret-core
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Obie Fernandez
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: hames
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.1'
|
|
26
|
+
description: 'The core of the Terret agent harness: an append-only session log that
|
|
27
|
+
is the single source of model-visible truth, a scoped tool registry with a vetoable
|
|
28
|
+
execution pipeline, the agent turn/step loop as a replaceable plugin, and a provider-neutral
|
|
29
|
+
LLM seam.'
|
|
30
|
+
email:
|
|
31
|
+
- obiefernandez@gmail.com
|
|
32
|
+
executables: []
|
|
33
|
+
extensions: []
|
|
34
|
+
extra_rdoc_files: []
|
|
35
|
+
files:
|
|
36
|
+
- lib/terret.rb
|
|
37
|
+
- lib/terret/llm.rb
|
|
38
|
+
- lib/terret/loop.rb
|
|
39
|
+
- lib/terret/sessions.rb
|
|
40
|
+
- lib/terret/tools.rb
|
|
41
|
+
homepage: https://terret.org
|
|
42
|
+
licenses:
|
|
43
|
+
- MIT
|
|
44
|
+
metadata:
|
|
45
|
+
homepage_uri: https://terret.org
|
|
46
|
+
source_code_uri: https://github.com/terret-org/terret
|
|
47
|
+
bug_tracker_uri: https://github.com/terret-org/terret/issues
|
|
48
|
+
rubygems_mfa_required: 'true'
|
|
49
|
+
rdoc_options: []
|
|
50
|
+
require_paths:
|
|
51
|
+
- lib
|
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
53
|
+
requirements:
|
|
54
|
+
- - ">="
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: '4.0'
|
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
requirements: []
|
|
63
|
+
rubygems_version: 4.0.16
|
|
64
|
+
specification_version: 4
|
|
65
|
+
summary: 'Terret agent harness core: session log, tools pipeline, agent loop'
|
|
66
|
+
test_files: []
|