riffer 0.30.0 → 0.31.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/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +7 -0
- data/docs/15_SERIALIZATION.md +23 -12
- data/lib/riffer/agent/serializer.rb +34 -24
- data/lib/riffer/agent.rb +12 -12
- data/lib/riffer/version.rb +1 -1
- data/sig/generated/riffer/agent/serializer.rbs +27 -21
- data/sig/generated/riffer/agent.rbs +10 -10
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e3f857f2ab85b78d91685b84c1d239a0409291c6945f9cc7aae0b84a5dead2a6
|
|
4
|
+
data.tar.gz: 9ae3efd534a2b099de8268e0b0bc89e2c2682d6a7297ada4409a1056203cf9b3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bf1b4dbd17abdfb1873f48d7db5b4b13f6ebde5c7e0e40bad73d7e6ca96f51e0d10c1ed9fe50d8be4a0532bcde9d5eb0eebd3ee4d264b195dbbfa12d09e70f9a
|
|
7
|
+
data.tar.gz: 0226c06943e53b5386659917746d1cd256d83861ab340bd0377393d8a45310fd2da60dad090e15a4f6a23dfa1e865b4fdc84f3bd6c1e0df94eafdaf850e4b4ae
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.31.0](https://github.com/janeapp/riffer/compare/riffer/v0.30.0...riffer/v0.31.0) (2026-06-03)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* forward session: through Agent.from_h/from_json for history seeding ([#295](https://github.com/janeapp/riffer/issues/295)) ([0b2eaa2](https://github.com/janeapp/riffer/commit/0b2eaa27f5154bfd61891d4e93b8938f7dce2ce6))
|
|
14
|
+
|
|
8
15
|
## [0.30.0](https://github.com/janeapp/riffer/compare/riffer/v0.29.1...riffer/v0.30.0) (2026-06-03)
|
|
9
16
|
|
|
10
17
|
|
data/docs/15_SERIALIZATION.md
CHANGED
|
@@ -1,28 +1,39 @@
|
|
|
1
1
|
# Serialization
|
|
2
2
|
|
|
3
|
-
`Riffer::Agent::Serializer` turns a **resolved agent** into a self-contained, provider-neutral data
|
|
3
|
+
`Riffer::Agent::Serializer` turns a **resolved agent** into a self-contained, provider-neutral data hash (`to_h`) and reconstructs a **runnable agent** from that hash (`from_h`). Use it to persist agent definitions outside of code, or to transfer them across a process/service boundary.
|
|
4
4
|
|
|
5
5
|
You normally reach it through the delegators on `Riffer::Agent`:
|
|
6
6
|
|
|
7
7
|
```ruby
|
|
8
|
-
|
|
9
|
-
rebuilt = Riffer::Agent.from_h(
|
|
8
|
+
data = agent.to_h # snapshot
|
|
9
|
+
rebuilt = Riffer::Agent.from_h(data) # reconstruct
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
The
|
|
12
|
+
The hash is plain data — symbol-keyed, JSON-safe. For the wire, use the JSON helpers, which handle generating and parsing for you:
|
|
13
13
|
|
|
14
14
|
```ruby
|
|
15
15
|
json = agent.to_json # or Riffer::Agent::Serializer.to_json(agent:)
|
|
16
16
|
rebuilt = Riffer::Agent.from_json(json)
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
The hash forms (`to_h` / `from_h`) are public too, if you want to embed the
|
|
19
|
+
The hash forms (`to_h` / `from_h`) are public too, if you want to embed the hash in a larger payload. `from_h` expects symbol keys, so parse with `JSON.parse(str, symbolize_names: true)` — or just use `from_json`, which does that for you.
|
|
20
20
|
|
|
21
21
|
### Runtime context
|
|
22
22
|
|
|
23
23
|
`from_h` / `from_json` accept an optional `context:` — the rebuilt agent's **runtime** context, exactly the value you'd pass to `Agent.new(context:)`. It is **not** used to re-resolve the serialized definition (that's already resolved); it's threaded into tool dispatch and read by tools/runtimes at call time. Pass it when a tool or a remote runtime needs per-call data — e.g. `context: { tenant: "acme" }` for multi-tenant dispatch, or Maestro passing `context: { agent: self }` so its runtime can call back. Omit it (defaults to empty) when nothing downstream reads context.
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
### Seeding conversation history
|
|
26
|
+
|
|
27
|
+
The hash carries the agent **definition**, not its conversation history (see [What does not transfer](#what-does-not-transfer)). To resume a persisted conversation, pass a `session:` — exactly the value you'd pass to `Agent.new(session:)`:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
rebuilt = Riffer::Agent.from_h(data, session: persisted_session)
|
|
31
|
+
rebuilt.generate # continues from the seeded history
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The session is used **as-is**: the rebuilt agent does not prepend anything to it, so the caller owns its full contents — **including the system instruction message**. Omit `session:` and the rebuilt agent builds a fresh session, seeded with the hash's `instructions` and an empty history. Because a supplied session is authoritative, the hash's `instructions` are not re-injected into it — make sure your persisted session already contains the system message.
|
|
35
|
+
|
|
36
|
+
## What the hash carries
|
|
26
37
|
|
|
27
38
|
```ruby
|
|
28
39
|
{
|
|
@@ -41,7 +52,7 @@ The hash forms (`to_h` / `from_h`) are public too, if you want to embed the dict
|
|
|
41
52
|
|
|
42
53
|
### Resolved snapshot
|
|
43
54
|
|
|
44
|
-
`to_h` reads the agent's **resolved** state, not its raw configuration. By the time you call it, `Agent.new` has already evaluated any `Proc`-based `model`, `instructions`, or `uses_tools` against the agent's own context — so the
|
|
55
|
+
`to_h` reads the agent's **resolved** state, not its raw configuration. By the time you call it, `Agent.new` has already evaluated any `Proc`-based `model`, `instructions`, or `uses_tools` against the agent's own context — so the hash carries plain strings and data, never Procs. The receiver's `context:` drives runtime behavior (tool dispatch); it does **not** re-evaluate baked-in fields.
|
|
45
56
|
|
|
46
57
|
### Structured output
|
|
47
58
|
|
|
@@ -56,7 +67,7 @@ Tools cross as `{name, description, parameters_schema, timeout}` descriptors —
|
|
|
56
67
|
When the rebuilt agent runs in the **same** codebase that defined the tools (e.g. persisting an agent definition and rehydrating it later), resolve each descriptor back to its real class:
|
|
57
68
|
|
|
58
69
|
```ruby
|
|
59
|
-
rebuilt = Riffer::Agent.from_h(
|
|
70
|
+
rebuilt = Riffer::Agent.from_h(data,
|
|
60
71
|
tool_resolver: ->(descriptor) { MyToolRegistry.fetch(descriptor[:name]) })
|
|
61
72
|
```
|
|
62
73
|
|
|
@@ -67,7 +78,7 @@ The real classes carry their `#call` bodies, so the agent runs on the default `I
|
|
|
67
78
|
When the receiver holds **only the Riffer gem**, the default `tool_resolver` synthesizes body-less **tool shells**. A shell advertises the tool's schema to the LLM but has no `#call` — invoking it in-process raises. Pair the default resolver with a remote `Riffer::Tools::Runtime` that forwards each call back to the origin:
|
|
68
79
|
|
|
69
80
|
```ruby
|
|
70
|
-
rebuilt = Riffer::Agent.from_h(
|
|
81
|
+
rebuilt = Riffer::Agent.from_h(data,
|
|
71
82
|
tool_runtime: MyRemoteToolRuntime.new(client: rpc_client))
|
|
72
83
|
```
|
|
73
84
|
|
|
@@ -77,12 +88,12 @@ You own what a resolved tool does: a resolver may return real in-process classes
|
|
|
77
88
|
|
|
78
89
|
## `max_steps`
|
|
79
90
|
|
|
80
|
-
Unlimited steps are `nil` at the agent level — set it with `max_steps nil`. On the wire, the serializer encodes that as **`-1`** (and decodes `-1` back to `nil`), so the
|
|
91
|
+
Unlimited steps are `nil` at the agent level — set it with `max_steps nil`. On the wire, the serializer encodes that as **`-1`** (and decodes `-1` back to `nil`), so the hash stays portable across transports where JSON `null` is awkward — proto3, for one, can't distinguish `null` from an absent field. The `-1` is purely a wire detail: the DSL and your code only ever see `nil`, and the encode/decode handles the translation at the boundary.
|
|
81
92
|
|
|
82
93
|
- **DSL** — integer = bounded, `nil` = unlimited, omitted = `Config`'s default (16).
|
|
83
94
|
- **Wire** — integer = bounded, `-1` = unlimited, omitted = default (16).
|
|
84
95
|
|
|
85
|
-
A finite integer round-trips as-is; a
|
|
96
|
+
A finite integer round-trips as-is; a hash missing the key falls back to the default rather than running unbounded.
|
|
86
97
|
|
|
87
98
|
## Versioning
|
|
88
99
|
|
|
@@ -90,7 +101,7 @@ A finite integer round-trips as-is; a dict missing the key falls back to the def
|
|
|
90
101
|
|
|
91
102
|
## Secrets
|
|
92
103
|
|
|
93
|
-
`provider_options` and `model_options` **ride on the wire as plain data** — they are part of the
|
|
104
|
+
`provider_options` and `model_options` **ride on the wire as plain data** — they are part of the hash and _will_ transfer. Prefer configuring API keys via environment/global provider configuration rather than `provider_options`. **Never serialize an agent whose options carry sensitive values** — and if a serialized definition ever does, handle it as a secret (encrypt it, keep it out of logs).
|
|
94
105
|
|
|
95
106
|
## What does **not** transfer
|
|
96
107
|
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
require "json"
|
|
5
5
|
|
|
6
6
|
# Riffer::Agent::Serializer turns a resolved agent into a self-contained,
|
|
7
|
-
# provider-neutral data
|
|
7
|
+
# provider-neutral data hash and back into a runnable agent. A pure module
|
|
8
8
|
# (sibling to Riffer::Agent::Run), reached most often through the
|
|
9
9
|
# +Riffer::Agent#to_h+ / +Riffer::Agent.from_h+ delegators.
|
|
10
10
|
#
|
|
11
|
-
# The
|
|
12
|
-
# runtime. The same
|
|
11
|
+
# The hash carries only data — no Procs, no class references, no tool
|
|
12
|
+
# runtime. The same hash serves two rehydration targets:
|
|
13
13
|
#
|
|
14
14
|
# - <b>In-process</b> (a monolith persisting agent definitions): pass a
|
|
15
15
|
# +tool_resolver+ that looks tool descriptors up in a local registry and
|
|
@@ -18,8 +18,8 @@ require "json"
|
|
|
18
18
|
# resolver synthesizes body-less tool shells; inject a remote
|
|
19
19
|
# +Riffer::Tools::Runtime+ to forward each call back to the origin.
|
|
20
20
|
#
|
|
21
|
-
#
|
|
22
|
-
# rebuilt = Riffer::Agent::Serializer.from_h(
|
|
21
|
+
# data = Riffer::Agent::Serializer.to_h(agent: agent)
|
|
22
|
+
# rebuilt = Riffer::Agent::Serializer.from_h(data, context: {tenant: "acme"})
|
|
23
23
|
#
|
|
24
24
|
# == What does not transfer
|
|
25
25
|
#
|
|
@@ -32,21 +32,21 @@ module Riffer::Agent::Serializer
|
|
|
32
32
|
extend self
|
|
33
33
|
|
|
34
34
|
# The wire format version. Bumped only on an incompatible change to the
|
|
35
|
-
#
|
|
35
|
+
# hash shape; +from_h+ refuses any other version. See +from_h+ for the
|
|
36
36
|
# dispatch seam that carries back-compat decoders.
|
|
37
37
|
SCHEMA_VERSION = 1 #: Integer
|
|
38
38
|
|
|
39
|
-
# Raised by +from_h+ when the
|
|
39
|
+
# Raised by +from_h+ when the hash's +schema_version+ is unsupported.
|
|
40
40
|
class VersionError < Riffer::ArgumentError; end
|
|
41
41
|
|
|
42
42
|
# The default +tool_resolver+: synthesizes a body-less tool shell from a
|
|
43
43
|
# descriptor. Its +#call+ raises — route shells through a remote runtime.
|
|
44
44
|
DEFAULT_TOOL_RESOLVER = ->(descriptor) { build_tool_shell(descriptor) } #: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool)
|
|
45
45
|
|
|
46
|
-
# Snapshots a resolved agent into a self-contained wire
|
|
46
|
+
# Snapshots a resolved agent into a self-contained wire hash.
|
|
47
47
|
#
|
|
48
48
|
# Reads the agent's resolved instance state — Proc-based settings have
|
|
49
|
-
# already been evaluated against the agent's own context, so the
|
|
49
|
+
# already been evaluated against the agent's own context, so the hash
|
|
50
50
|
# carries plain strings/data, never Procs. Tools are emitted as
|
|
51
51
|
# +{name, description, parameters_schema, timeout}+ descriptors (the
|
|
52
52
|
# resolved +agent.tools+, including MCP tools and +skill_activate+).
|
|
@@ -71,14 +71,20 @@ module Riffer::Agent::Serializer
|
|
|
71
71
|
}
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
-
# Reconstructs a runnable agent from a wire
|
|
74
|
+
# Reconstructs a runnable agent from a wire hash.
|
|
75
75
|
#
|
|
76
|
-
# [hash] a Symbol-keyed wire
|
|
76
|
+
# [hash] a Symbol-keyed wire hash (parse JSON with +symbolize_names: true+).
|
|
77
77
|
# [context] the rebuilt agent's runtime context — the same value you'd pass
|
|
78
78
|
# to +Agent.new(context:)+. It is *not* used to re-resolve serialized
|
|
79
|
-
# config (the
|
|
79
|
+
# config (the hash is already resolved); it is threaded into tool dispatch
|
|
80
80
|
# and read by tools/runtimes at call time (e.g. a remote runtime keying off
|
|
81
81
|
# <tt>context[:tenant]</tt>). Defaults to an empty context.
|
|
82
|
+
# [session] an optional Riffer::Agent::Session to seed conversation history,
|
|
83
|
+
# forwarded verbatim to +Agent.new(session:)+. The hash carries the agent
|
|
84
|
+
# *definition*, not its history (see "What does not transfer"); pass a
|
|
85
|
+
# session here to resume a persisted conversation. Used as-is — the caller
|
|
86
|
+
# owns its contents, including the system instruction message. When omitted,
|
|
87
|
+
# a fresh session seeded with the hash's instructions is built.
|
|
82
88
|
# [tool_resolver] maps a tool descriptor to a Riffer::Tool class. Defaults
|
|
83
89
|
# to DEFAULT_TOOL_RESOLVER (body-less shells). Pass a registry lookup to
|
|
84
90
|
# rebuild real, in-process tools.
|
|
@@ -87,16 +93,16 @@ module Riffer::Agent::Serializer
|
|
|
87
93
|
# default (+Riffer.config.tool_runtime+).
|
|
88
94
|
#
|
|
89
95
|
# Raises Riffer::Agent::Serializer::VersionError on an unsupported
|
|
90
|
-
# +schema_version+, and Riffer::ArgumentError on a malformed
|
|
96
|
+
# +schema_version+, and Riffer::ArgumentError on a malformed hash.
|
|
91
97
|
#
|
|
92
98
|
#--
|
|
93
|
-
#: (Hash[Symbol, untyped], ?context: Hash[Symbol, untyped]?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
94
|
-
def from_h(hash, context: nil, tool_resolver: DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
|
|
99
|
+
#: (Hash[Symbol, untyped], ?context: Hash[Symbol, untyped]?, ?session: Riffer::Agent::Session?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
100
|
+
def from_h(hash, context: nil, session: nil, tool_resolver: DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
|
|
95
101
|
# Version -> decoder dispatch. Adding a +when 2+ arm (a backwards-compatible
|
|
96
102
|
# decoder) is how a future breaking change keeps older dicts readable.
|
|
97
103
|
case hash[:schema_version]
|
|
98
104
|
when SCHEMA_VERSION
|
|
99
|
-
decode_v1(hash, context: context, tool_resolver: tool_resolver, tool_runtime: tool_runtime)
|
|
105
|
+
decode_v1(hash, context: context, session: session, tool_resolver: tool_resolver, tool_runtime: tool_runtime)
|
|
100
106
|
else
|
|
101
107
|
raise VersionError, "Unsupported schema_version: #{hash[:schema_version].inspect} (this Riffer supports #{SCHEMA_VERSION})"
|
|
102
108
|
end
|
|
@@ -116,16 +122,16 @@ module Riffer::Agent::Serializer
|
|
|
116
122
|
# +from_h+ for the arguments.
|
|
117
123
|
#
|
|
118
124
|
#--
|
|
119
|
-
#: (String, ?context: Hash[Symbol, untyped]?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
120
|
-
def from_json(json, context: nil, tool_resolver: DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
|
|
121
|
-
from_h(JSON.parse(json, symbolize_names: true), context: context, tool_resolver: tool_resolver, tool_runtime: tool_runtime)
|
|
125
|
+
#: (String, ?context: Hash[Symbol, untyped]?, ?session: Riffer::Agent::Session?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
126
|
+
def from_json(json, context: nil, session: nil, tool_resolver: DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
|
|
127
|
+
from_h(JSON.parse(json, symbolize_names: true), context: context, session: session, tool_resolver: tool_resolver, tool_runtime: tool_runtime)
|
|
122
128
|
end
|
|
123
129
|
|
|
124
130
|
private
|
|
125
131
|
|
|
126
132
|
#--
|
|
127
|
-
#: (Hash[Symbol, untyped], context: Hash[Symbol, untyped]?, tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
128
|
-
def decode_v1(hash, context:, tool_resolver:, tool_runtime:)
|
|
133
|
+
#: (Hash[Symbol, untyped], context: Hash[Symbol, untyped]?, session: Riffer::Agent::Session?, tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
134
|
+
def decode_v1(hash, context:, session:, tool_resolver:, tool_runtime:)
|
|
129
135
|
tools = Array(hash[:tools]).map { |descriptor| tool_resolver.call(descriptor) }
|
|
130
136
|
|
|
131
137
|
config_args = {
|
|
@@ -142,7 +148,11 @@ module Riffer::Agent::Serializer
|
|
|
142
148
|
# Config default (Riffer.config.tool_runtime) applies.
|
|
143
149
|
config_args[:tool_runtime] = tool_runtime if tool_runtime
|
|
144
150
|
|
|
145
|
-
|
|
151
|
+
# +session+ is forwarded verbatim: when nil, Agent.new seeds a fresh session
|
|
152
|
+
# from the decoded instructions; when supplied, Agent.new uses it as-is to
|
|
153
|
+
# resume persisted history. The hash never carries history (see "What does
|
|
154
|
+
# not transfer"), so this is the only seam for rehydrating a conversation.
|
|
155
|
+
Riffer::Agent.new(config: Riffer::Agent::Config.new(**config_args), context: context, session: session)
|
|
146
156
|
end
|
|
147
157
|
|
|
148
158
|
#--
|
|
@@ -153,7 +163,7 @@ module Riffer::Agent::Serializer
|
|
|
153
163
|
end
|
|
154
164
|
|
|
155
165
|
# The DSL represents unlimited steps as +nil+, but the wire encodes it as
|
|
156
|
-
# +-1+ so the
|
|
166
|
+
# +-1+ so the hash stays portable across transports where JSON +null+ is
|
|
157
167
|
# awkward (e.g. proto3, which can't tell null from an absent field). The
|
|
158
168
|
# magic value lives only on the wire — +encode_max_steps+/+decode_max_steps+
|
|
159
169
|
# translate at the boundary so neither the DSL nor consumers see it.
|
|
@@ -164,7 +174,7 @@ module Riffer::Agent::Serializer
|
|
|
164
174
|
end
|
|
165
175
|
|
|
166
176
|
# Reverses +encode_max_steps+: +-1+ (or a literal +null+) means unlimited.
|
|
167
|
-
# An absent key falls back to the default — a partial
|
|
177
|
+
# An absent key falls back to the default — a partial hash must not silently
|
|
168
178
|
# become an unbounded loop.
|
|
169
179
|
#--
|
|
170
180
|
#: (Hash[Symbol, untyped]) -> Numeric?
|
data/lib/riffer/agent.rb
CHANGED
|
@@ -212,28 +212,28 @@ class Riffer::Agent
|
|
|
212
212
|
new(context: context).stream(prompt, files: files)
|
|
213
213
|
end
|
|
214
214
|
|
|
215
|
-
# Reconstructs a runnable agent from a wire
|
|
215
|
+
# Reconstructs a runnable agent from a wire hash produced by +#to_h+.
|
|
216
216
|
#
|
|
217
|
-
# Delegates to Riffer::Agent::Serializer.from_h. See it for the
|
|
218
|
-
# +tool_resolver+ / +tool_runtime+ injection points and what does
|
|
219
|
-
# transfer.
|
|
217
|
+
# Delegates to Riffer::Agent::Serializer.from_h. See it for the +session+
|
|
218
|
+
# seed, the +tool_resolver+ / +tool_runtime+ injection points, and what does
|
|
219
|
+
# not transfer.
|
|
220
220
|
#
|
|
221
221
|
#--
|
|
222
|
-
#: (Hash[Symbol, untyped], ?context: Hash[Symbol, untyped]?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
223
|
-
def self.from_h(hash, context: nil, tool_resolver: Riffer::Agent::Serializer::DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
|
|
224
|
-
Riffer::Agent::Serializer.from_h(hash, context: context, tool_resolver: tool_resolver, tool_runtime: tool_runtime)
|
|
222
|
+
#: (Hash[Symbol, untyped], ?context: Hash[Symbol, untyped]?, ?session: Riffer::Agent::Session?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
223
|
+
def self.from_h(hash, context: nil, session: nil, tool_resolver: Riffer::Agent::Serializer::DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
|
|
224
|
+
Riffer::Agent::Serializer.from_h(hash, context: context, session: session, tool_resolver: tool_resolver, tool_runtime: tool_runtime)
|
|
225
225
|
end
|
|
226
226
|
|
|
227
227
|
# Reconstructs a runnable agent from a JSON string produced by +#to_json+.
|
|
228
228
|
#
|
|
229
229
|
# Delegates to Riffer::Agent::Serializer.from_json, which parses the JSON
|
|
230
230
|
# (with symbol keys) for you. See Riffer::Agent::Serializer.from_h for the
|
|
231
|
-
# +tool_resolver+ / +tool_runtime+ injection points.
|
|
231
|
+
# +session+ seed and the +tool_resolver+ / +tool_runtime+ injection points.
|
|
232
232
|
#
|
|
233
233
|
#--
|
|
234
|
-
#: (String, ?context: Hash[Symbol, untyped]?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
235
|
-
def self.from_json(json, context: nil, tool_resolver: Riffer::Agent::Serializer::DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
|
|
236
|
-
Riffer::Agent::Serializer.from_json(json, context: context, tool_resolver: tool_resolver, tool_runtime: tool_runtime)
|
|
234
|
+
#: (String, ?context: Hash[Symbol, untyped]?, ?session: Riffer::Agent::Session?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
235
|
+
def self.from_json(json, context: nil, session: nil, tool_resolver: Riffer::Agent::Serializer::DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
|
|
236
|
+
Riffer::Agent::Serializer.from_json(json, context: context, session: session, tool_resolver: tool_resolver, tool_runtime: tool_runtime)
|
|
237
237
|
end
|
|
238
238
|
|
|
239
239
|
# Registers a guardrail for input, output, or both phases.
|
|
@@ -412,7 +412,7 @@ class Riffer::Agent
|
|
|
412
412
|
end
|
|
413
413
|
|
|
414
414
|
# Snapshots this resolved agent into a self-contained, provider-neutral
|
|
415
|
-
# wire
|
|
415
|
+
# wire hash. Delegates to Riffer::Agent::Serializer.to_h.
|
|
416
416
|
#
|
|
417
417
|
#--
|
|
418
418
|
#: () -> Hash[Symbol, untyped]
|
data/lib/riffer/version.rb
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Generated from lib/riffer/agent/serializer.rb with RBS::Inline
|
|
2
2
|
|
|
3
3
|
# Riffer::Agent::Serializer turns a resolved agent into a self-contained,
|
|
4
|
-
# provider-neutral data
|
|
4
|
+
# provider-neutral data hash and back into a runnable agent. A pure module
|
|
5
5
|
# (sibling to Riffer::Agent::Run), reached most often through the
|
|
6
6
|
# +Riffer::Agent#to_h+ / +Riffer::Agent.from_h+ delegators.
|
|
7
7
|
#
|
|
8
|
-
# The
|
|
9
|
-
# runtime. The same
|
|
8
|
+
# The hash carries only data — no Procs, no class references, no tool
|
|
9
|
+
# runtime. The same hash serves two rehydration targets:
|
|
10
10
|
#
|
|
11
11
|
# - <b>In-process</b> (a monolith persisting agent definitions): pass a
|
|
12
12
|
# +tool_resolver+ that looks tool descriptors up in a local registry and
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
# resolver synthesizes body-less tool shells; inject a remote
|
|
16
16
|
# +Riffer::Tools::Runtime+ to forward each call back to the origin.
|
|
17
17
|
#
|
|
18
|
-
#
|
|
19
|
-
# rebuilt = Riffer::Agent::Serializer.from_h(
|
|
18
|
+
# data = Riffer::Agent::Serializer.to_h(agent: agent)
|
|
19
|
+
# rebuilt = Riffer::Agent::Serializer.from_h(data, context: {tenant: "acme"})
|
|
20
20
|
#
|
|
21
21
|
# == What does not transfer
|
|
22
22
|
#
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
# both ride on the wire as plain data.
|
|
28
28
|
module Riffer::Agent::Serializer
|
|
29
29
|
# The wire format version. Bumped only on an incompatible change to the
|
|
30
|
-
#
|
|
30
|
+
# hash shape; +from_h+ refuses any other version. See +from_h+ for the
|
|
31
31
|
# dispatch seam that carries back-compat decoders.
|
|
32
32
|
SCHEMA_VERSION: Integer
|
|
33
33
|
|
|
34
|
-
# Raised by +from_h+ when the
|
|
34
|
+
# Raised by +from_h+ when the hash's +schema_version+ is unsupported.
|
|
35
35
|
class VersionError < Riffer::ArgumentError
|
|
36
36
|
end
|
|
37
37
|
|
|
@@ -39,10 +39,10 @@ module Riffer::Agent::Serializer
|
|
|
39
39
|
# descriptor. Its +#call+ raises — route shells through a remote runtime.
|
|
40
40
|
DEFAULT_TOOL_RESOLVER: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool)
|
|
41
41
|
|
|
42
|
-
# Snapshots a resolved agent into a self-contained wire
|
|
42
|
+
# Snapshots a resolved agent into a self-contained wire hash.
|
|
43
43
|
#
|
|
44
44
|
# Reads the agent's resolved instance state — Proc-based settings have
|
|
45
|
-
# already been evaluated against the agent's own context, so the
|
|
45
|
+
# already been evaluated against the agent's own context, so the hash
|
|
46
46
|
# carries plain strings/data, never Procs. Tools are emitted as
|
|
47
47
|
# +{name, description, parameters_schema, timeout}+ descriptors (the
|
|
48
48
|
# resolved +agent.tools+, including MCP tools and +skill_activate+).
|
|
@@ -53,14 +53,20 @@ module Riffer::Agent::Serializer
|
|
|
53
53
|
# : (agent: Riffer::Agent) -> Hash[Symbol, untyped]
|
|
54
54
|
def to_h: (agent: Riffer::Agent) -> Hash[Symbol, untyped]
|
|
55
55
|
|
|
56
|
-
# Reconstructs a runnable agent from a wire
|
|
56
|
+
# Reconstructs a runnable agent from a wire hash.
|
|
57
57
|
#
|
|
58
|
-
# [hash] a Symbol-keyed wire
|
|
58
|
+
# [hash] a Symbol-keyed wire hash (parse JSON with +symbolize_names: true+).
|
|
59
59
|
# [context] the rebuilt agent's runtime context — the same value you'd pass
|
|
60
60
|
# to +Agent.new(context:)+. It is *not* used to re-resolve serialized
|
|
61
|
-
# config (the
|
|
61
|
+
# config (the hash is already resolved); it is threaded into tool dispatch
|
|
62
62
|
# and read by tools/runtimes at call time (e.g. a remote runtime keying off
|
|
63
63
|
# <tt>context[:tenant]</tt>). Defaults to an empty context.
|
|
64
|
+
# [session] an optional Riffer::Agent::Session to seed conversation history,
|
|
65
|
+
# forwarded verbatim to +Agent.new(session:)+. The hash carries the agent
|
|
66
|
+
# *definition*, not its history (see "What does not transfer"); pass a
|
|
67
|
+
# session here to resume a persisted conversation. Used as-is — the caller
|
|
68
|
+
# owns its contents, including the system instruction message. When omitted,
|
|
69
|
+
# a fresh session seeded with the hash's instructions is built.
|
|
64
70
|
# [tool_resolver] maps a tool descriptor to a Riffer::Tool class. Defaults
|
|
65
71
|
# to DEFAULT_TOOL_RESOLVER (body-less shells). Pass a registry lookup to
|
|
66
72
|
# rebuild real, in-process tools.
|
|
@@ -69,11 +75,11 @@ module Riffer::Agent::Serializer
|
|
|
69
75
|
# default (+Riffer.config.tool_runtime+).
|
|
70
76
|
#
|
|
71
77
|
# Raises Riffer::Agent::Serializer::VersionError on an unsupported
|
|
72
|
-
# +schema_version+, and Riffer::ArgumentError on a malformed
|
|
78
|
+
# +schema_version+, and Riffer::ArgumentError on a malformed hash.
|
|
73
79
|
#
|
|
74
80
|
# --
|
|
75
|
-
# : (Hash[Symbol, untyped], ?context: Hash[Symbol, untyped]?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
76
|
-
def from_h: (Hash[Symbol, untyped], ?context: Hash[Symbol, untyped]?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
81
|
+
# : (Hash[Symbol, untyped], ?context: Hash[Symbol, untyped]?, ?session: Riffer::Agent::Session?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
82
|
+
def from_h: (Hash[Symbol, untyped], ?context: Hash[Symbol, untyped]?, ?session: Riffer::Agent::Session?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
77
83
|
|
|
78
84
|
# Snapshots a resolved agent to a JSON string. Convenience over
|
|
79
85
|
# <tt>JSON.generate(to_h(agent:))</tt>.
|
|
@@ -87,21 +93,21 @@ module Riffer::Agent::Serializer
|
|
|
87
93
|
# +from_h+ for the arguments.
|
|
88
94
|
#
|
|
89
95
|
# --
|
|
90
|
-
# : (String, ?context: Hash[Symbol, untyped]?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
91
|
-
def from_json: (String, ?context: Hash[Symbol, untyped]?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
96
|
+
# : (String, ?context: Hash[Symbol, untyped]?, ?session: Riffer::Agent::Session?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
97
|
+
def from_json: (String, ?context: Hash[Symbol, untyped]?, ?session: Riffer::Agent::Session?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
92
98
|
|
|
93
99
|
private
|
|
94
100
|
|
|
95
101
|
# --
|
|
96
|
-
# : (Hash[Symbol, untyped], context: Hash[Symbol, untyped]?, tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
97
|
-
def decode_v1: (Hash[Symbol, untyped], context: Hash[Symbol, untyped]?, tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
102
|
+
# : (Hash[Symbol, untyped], context: Hash[Symbol, untyped]?, session: Riffer::Agent::Session?, tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
103
|
+
def decode_v1: (Hash[Symbol, untyped], context: Hash[Symbol, untyped]?, session: Riffer::Agent::Session?, tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
98
104
|
|
|
99
105
|
# --
|
|
100
106
|
# : (Hash[Symbol, untyped]?) -> Riffer::Params?
|
|
101
107
|
def decode_structured_output: (Hash[Symbol, untyped]?) -> Riffer::Params?
|
|
102
108
|
|
|
103
109
|
# The DSL represents unlimited steps as +nil+, but the wire encodes it as
|
|
104
|
-
# +-1+ so the
|
|
110
|
+
# +-1+ so the hash stays portable across transports where JSON +null+ is
|
|
105
111
|
# awkward (e.g. proto3, which can't tell null from an absent field). The
|
|
106
112
|
# magic value lives only on the wire — +encode_max_steps+/+decode_max_steps+
|
|
107
113
|
# translate at the boundary so neither the DSL nor consumers see it.
|
|
@@ -110,7 +116,7 @@ module Riffer::Agent::Serializer
|
|
|
110
116
|
def encode_max_steps: (Numeric?) -> Numeric
|
|
111
117
|
|
|
112
118
|
# Reverses +encode_max_steps+: +-1+ (or a literal +null+) means unlimited.
|
|
113
|
-
# An absent key falls back to the default — a partial
|
|
119
|
+
# An absent key falls back to the default — a partial hash must not silently
|
|
114
120
|
# become an unbounded loop.
|
|
115
121
|
# --
|
|
116
122
|
# : (Hash[Symbol, untyped]) -> Numeric?
|
|
@@ -164,25 +164,25 @@ class Riffer::Agent
|
|
|
164
164
|
# : (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?context: Hash[Symbol, untyped]?) -> Enumerator[Riffer::StreamEvents::Base, void]
|
|
165
165
|
def self.stream: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?context: Hash[Symbol, untyped]?) -> Enumerator[Riffer::StreamEvents::Base, void]
|
|
166
166
|
|
|
167
|
-
# Reconstructs a runnable agent from a wire
|
|
167
|
+
# Reconstructs a runnable agent from a wire hash produced by +#to_h+.
|
|
168
168
|
#
|
|
169
|
-
# Delegates to Riffer::Agent::Serializer.from_h. See it for the
|
|
170
|
-
# +tool_resolver+ / +tool_runtime+ injection points and what does
|
|
171
|
-
# transfer.
|
|
169
|
+
# Delegates to Riffer::Agent::Serializer.from_h. See it for the +session+
|
|
170
|
+
# seed, the +tool_resolver+ / +tool_runtime+ injection points, and what does
|
|
171
|
+
# not transfer.
|
|
172
172
|
#
|
|
173
173
|
# --
|
|
174
|
-
# : (Hash[Symbol, untyped], ?context: Hash[Symbol, untyped]?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
175
|
-
def self.from_h: (Hash[Symbol, untyped], ?context: Hash[Symbol, untyped]?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
174
|
+
# : (Hash[Symbol, untyped], ?context: Hash[Symbol, untyped]?, ?session: Riffer::Agent::Session?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
175
|
+
def self.from_h: (Hash[Symbol, untyped], ?context: Hash[Symbol, untyped]?, ?session: Riffer::Agent::Session?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
176
176
|
|
|
177
177
|
# Reconstructs a runnable agent from a JSON string produced by +#to_json+.
|
|
178
178
|
#
|
|
179
179
|
# Delegates to Riffer::Agent::Serializer.from_json, which parses the JSON
|
|
180
180
|
# (with symbol keys) for you. See Riffer::Agent::Serializer.from_h for the
|
|
181
|
-
# +tool_resolver+ / +tool_runtime+ injection points.
|
|
181
|
+
# +session+ seed and the +tool_resolver+ / +tool_runtime+ injection points.
|
|
182
182
|
#
|
|
183
183
|
# --
|
|
184
|
-
# : (String, ?context: Hash[Symbol, untyped]?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
185
|
-
def self.from_json: (String, ?context: Hash[Symbol, untyped]?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
184
|
+
# : (String, ?context: Hash[Symbol, untyped]?, ?session: Riffer::Agent::Session?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
185
|
+
def self.from_json: (String, ?context: Hash[Symbol, untyped]?, ?session: Riffer::Agent::Session?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
186
186
|
|
|
187
187
|
# Registers a guardrail for input, output, or both phases.
|
|
188
188
|
#
|
|
@@ -331,7 +331,7 @@ class Riffer::Agent
|
|
|
331
331
|
def interrupt!: (?(String | Symbol)?) -> void
|
|
332
332
|
|
|
333
333
|
# Snapshots this resolved agent into a self-contained, provider-neutral
|
|
334
|
-
# wire
|
|
334
|
+
# wire hash. Delegates to Riffer::Agent::Serializer.to_h.
|
|
335
335
|
#
|
|
336
336
|
# --
|
|
337
337
|
# : () -> Hash[Symbol, untyped]
|