little_ghost 0.3.0 → 0.4.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/README.md +68 -84
- data/docs/guides/assemblies.md +286 -0
- data/docs/guides/core_concepts.md +126 -231
- data/docs/guides/getting_started.md +114 -87
- data/docs/guides/production.md +187 -0
- data/docs/guides/prompt_views.md +132 -0
- data/lib/little_ghost/ag_ui/adapter.rb +3 -3
- data/lib/little_ghost/agent/delegation.rb +1 -1
- data/lib/little_ghost/agent.rb +167 -172
- data/lib/little_ghost/{agent_interruptions.rb → agent_interjections.rb} +12 -12
- data/lib/little_ghost/assembly.rb +55 -21
- data/lib/little_ghost/assembly_builder.rb +40 -2
- data/lib/little_ghost/assembly_execution.rb +87 -4
- data/lib/little_ghost/configuration.rb +263 -39
- data/lib/little_ghost/content.rb +5 -5
- data/lib/little_ghost/data_map.rb +209 -0
- data/lib/little_ghost/errors.rb +2 -2
- data/lib/little_ghost/execution.rb +32 -32
- data/lib/little_ghost/graph.rb +22 -3
- data/lib/little_ghost/message.rb +4 -4
- data/lib/little_ghost/model_resolver.rb +2 -2
- data/lib/little_ghost/prompt_resolver.rb +2 -0
- data/lib/little_ghost/run.rb +87 -49
- data/lib/little_ghost/run_context.rb +33 -20
- data/lib/little_ghost/runtime/hook.rb +3 -3
- data/lib/little_ghost/runtime.rb +71 -31
- data/lib/little_ghost/session.rb +12 -23
- data/lib/little_ghost/session_store.rb +9 -5
- data/lib/little_ghost/session_stores/agent_core_memory.rb +64 -56
- data/lib/little_ghost/session_stores/filesystem.rb +261 -0
- data/lib/little_ghost/session_stores/memory.rb +7 -0
- data/lib/little_ghost/subagents/manager.rb +42 -42
- data/lib/little_ghost/swarm.rb +13 -5
- data/lib/little_ghost/tool.rb +56 -14
- data/lib/little_ghost/tools/write_todos.rb +6 -1
- data/lib/little_ghost/tracing/open_telemetry.rb +1 -1
- data/lib/little_ghost/version.rb +1 -1
- data/lib/little_ghost/workflow.rb +30 -21
- data/lib/little_ghost.rb +29 -25
- metadata +7 -2
|
@@ -1,182 +1,156 @@
|
|
|
1
1
|
# Core Concepts
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
```text
|
|
6
|
-
shared configuration
|
|
7
|
-
└── ModelResolver ── resolves model selections ──> provider clients
|
|
8
|
-
|
|
9
|
-
one request
|
|
10
|
-
└── Run
|
|
11
|
-
├── CustomerSupportAgent
|
|
12
|
-
│ ├── HelpCenterLookupTool
|
|
13
|
-
│ └── ResearchAgent subagent (model-directed)
|
|
14
|
-
└── sessions, resources, usage, events, and terminal result
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
The sections below build outward from that unit. After the agent, the guide introduces its model, tools, and run lifecycle. It then names an **assembly**: anything a caller can invoke like one agent, including coordinated workflows, swarms, and graphs.
|
|
18
|
-
|
|
19
|
-
## Agents declare one model-driven behavior
|
|
20
|
-
|
|
21
|
-
An agent class keeps the behavior for one application role together:
|
|
3
|
+
Build one model-driven behavior in a Ruby class, then call it like Ruby. That is the idea LittleGhost grows from.
|
|
22
4
|
|
|
23
5
|
```ruby
|
|
24
6
|
class CustomerSupportAgent < LittleGhost::Agent
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
system_prompt "Answer clearly. Check the help center before stating company guidance."
|
|
7
|
+
model "openrouter:openai/gpt-5.6-luna"
|
|
8
|
+
system_prompt "Answer customer questions clearly."
|
|
28
9
|
tools HelpCenterLookupTool
|
|
29
10
|
end
|
|
30
|
-
```
|
|
31
11
|
|
|
32
|
-
|
|
12
|
+
run = CustomerSupportAgent.ask("Where is my order?")
|
|
13
|
+
run.response
|
|
14
|
+
```
|
|
33
15
|
|
|
34
|
-
|
|
16
|
+
From there, add only what the work needs. Give the agent a tool. Let it ask a specialist for help. Or coordinate several agents while the rest of your application keeps making the same call.
|
|
35
17
|
|
|
36
|
-
|
|
37
|
-
run = CustomerSupportAgent.ask("Can I get a refund?")
|
|
38
|
-
run.response # final text from the top-level execution
|
|
39
|
-
run.result.output # text, or a validated structured value when declared
|
|
40
|
-
```
|
|
18
|
+
## An Agent owns one model loop
|
|
41
19
|
|
|
42
|
-
|
|
20
|
+
An **Agent** defines one model-driven behavior. It chooses the model, supplies the instructions and tools, and carries one request through to an answer.
|
|
43
21
|
|
|
44
|
-
|
|
22
|
+
The class holds the behavior you want to reuse. Each call brings its own input, history, context, settings, and attachments. Request data never needs to live on the class.
|
|
45
23
|
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
24
|
+
```text
|
|
25
|
+
CustomerSupportAgent
|
|
26
|
+
├── model selection
|
|
27
|
+
├── system prompt
|
|
28
|
+
├── HelpCenterLookupTool
|
|
29
|
+
└── limits and optional capabilities
|
|
50
30
|
```
|
|
51
31
|
|
|
52
|
-
|
|
32
|
+
An Agent can return text or checked, structured data. Later, you can add streaming, sessions, or callbacks. None of them are required to begin.
|
|
53
33
|
|
|
54
|
-
|
|
55
|
-
class DeliberateSupportAgent < LittleGhost::Agent
|
|
56
|
-
model(provider: "openai", model: "gpt-5.6-luna", reasoning_effort: "high")
|
|
57
|
-
end
|
|
58
|
-
```
|
|
34
|
+
## A Tool connects the model to Ruby
|
|
59
35
|
|
|
60
|
-
|
|
36
|
+
A **Tool** is one focused thing an agent can ask your application to do. It has a name, a description, an input schema, and the Ruby code that does the work.
|
|
61
37
|
|
|
62
38
|
```ruby
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
```
|
|
39
|
+
class HelpCenterLookupTool < LittleGhost::Tool
|
|
40
|
+
description "Look up a help center entry by topic."
|
|
41
|
+
input_schema(
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {topic: {type: "string"}},
|
|
44
|
+
required: ["topic"],
|
|
45
|
+
additionalProperties: false
|
|
46
|
+
)
|
|
72
47
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
48
|
+
def call(input)
|
|
49
|
+
{"refunds" => "Refunds are available within 30 days."}
|
|
50
|
+
.fetch(input.fetch("topic"))
|
|
51
|
+
end
|
|
76
52
|
end
|
|
77
53
|
```
|
|
78
54
|
|
|
79
|
-
|
|
55
|
+
LittleGhost checks the model's arguments, calls the tool, and gives the result back to the model. The schema checks shape, not permission. Authorize sensitive reads and actions inside the tool with trusted application context.
|
|
80
56
|
|
|
81
|
-
|
|
57
|
+
## A Run owns one top-level execution
|
|
82
58
|
|
|
83
|
-
|
|
59
|
+
Every `.ask` or `.stream_ask` creates a **Run**. Think of it as the record of one trip through LittleGhost. It opens what the request needs, records how the work ended, and closes the resources it owns.
|
|
84
60
|
|
|
85
|
-
|
|
61
|
+
```ruby
|
|
62
|
+
run = CustomerSupportAgent.ask("Where is order 481?")
|
|
86
63
|
|
|
87
|
-
|
|
64
|
+
run.completed? # => true
|
|
65
|
+
run.response
|
|
66
|
+
# One possible response: Order 481 is out for delivery.
|
|
67
|
+
run.usage # => normalized token usage
|
|
68
|
+
run.result # => the complete LittleGhost::RunResult
|
|
69
|
+
```
|
|
88
70
|
|
|
89
|
-
The
|
|
71
|
+
The Agent defines reusable behavior; the Run records what happened this time.
|
|
90
72
|
|
|
91
|
-
|
|
73
|
+
### Follow one request
|
|
92
74
|
|
|
93
|
-
|
|
94
|
-
execution = CustomerSupportAgent.new.start_execution(
|
|
95
|
-
message: "Investigate transfer 481"
|
|
96
|
-
) do |event|
|
|
97
|
-
event_buffer << event
|
|
98
|
-
end
|
|
75
|
+
One Run owns the trip from request to result:
|
|
99
76
|
|
|
100
|
-
|
|
101
|
-
|
|
77
|
+
```text
|
|
78
|
+
Run
|
|
79
|
+
├── Invocation: caller input, history, and application context
|
|
80
|
+
├── RunContext: mutable working state for this execution
|
|
81
|
+
└── Agent and Tools ──> RunResult
|
|
102
82
|
```
|
|
103
83
|
|
|
104
|
-
|
|
84
|
+
An **Invocation** is the request in LittleGhost's standard shape. Its `context` contains current request values supplied by your application. A Tool can read those values through `run.invocation.context` when it authorizes work.
|
|
105
85
|
|
|
106
|
-
|
|
86
|
+
The **RunContext** carries mutable working state in `context.state`. At the top level, saved Session state is loaded first, then current Invocation context is added. Child Assemblies may receive a copy, a mapped value, or no context at all. Recheck saved values before using them for permission decisions.
|
|
107
87
|
|
|
108
|
-
|
|
88
|
+
A Tool's **Binding** gives the Tool access to objects created for this run, including the Agent, Run, workspace, and sandbox. These objects are separate from the arguments chosen by the model.
|
|
109
89
|
|
|
110
|
-
`
|
|
90
|
+
The final **RunResult** keeps the complete assembly result. Its `text` is the final text answer. Its `output` returns structured data when the Agent declared a result schema, and text otherwise. The top-level `Run#response` is always the caller-facing text.
|
|
111
91
|
|
|
112
|
-
|
|
113
|
-
class HelpCenterLookupTool < LittleGhost::Tool
|
|
114
|
-
description "Look up a help center entry by topic."
|
|
115
|
-
input_schema(
|
|
116
|
-
type: "object",
|
|
117
|
-
properties: {topic: {type: "string"}},
|
|
118
|
-
required: ["topic"],
|
|
119
|
-
additionalProperties: false
|
|
120
|
-
)
|
|
92
|
+
### See how a call ended
|
|
121
93
|
|
|
122
|
-
|
|
123
|
-
HelpCenterRepository.fetch(input.fetch("topic"))
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
LittleGhost validates the model's input before invoking `#call`. Hashes and arrays returned by a tool are JSON-encoded; other values become text. Expected application failures can raise `LittleGhost::ToolError`; unexpected exception messages are sanitized before they reach model context.
|
|
94
|
+
Top-level calls normally return a Run, even when execution fails. The terminal event carries the same outcome when you stream:
|
|
129
95
|
|
|
130
|
-
|
|
96
|
+
| What happened | Run outcome | Terminal event | What Ruby does |
|
|
97
|
+
| --- | --- | --- | --- |
|
|
98
|
+
| The assembly completed | `completed` | `:run_stop` | Returns the Run |
|
|
99
|
+
| Model, provider, or assembly execution failed | `failed` | `:run_error` | Returns the Run; inspect `run.error` |
|
|
100
|
+
| The deadline stopped work | `partial` | `:run_partial` | Returns the Run with any response produced so far |
|
|
101
|
+
| Cancellation stopped work | `cancelled` | `:run_cancel` | Returns the Run without a response |
|
|
102
|
+
| Tool input or a `ToolError` failed | The model may recover | No terminal event by itself | Gives a safe error result back to the model |
|
|
103
|
+
| Input, configuration, or resources failed before a Run could start | No Run exists | None | Raises the exception |
|
|
131
104
|
|
|
132
|
-
|
|
105
|
+
Unexpected Tool exception messages are hidden from the model. The original exception remains available to trusted application callbacks and diagnostics.
|
|
133
106
|
|
|
134
|
-
|
|
107
|
+
Failures while closing resources, delivering events, or reporting instrumentation sit outside the normal result path. They raise a Ruby exception because LittleGhost can no longer promise that it delivered a clean ending. [Running in Production](production.md) covers that boundary where applications supervise and shut down work.
|
|
135
108
|
|
|
136
|
-
An
|
|
109
|
+
## An Assembly can look like one Agent
|
|
137
110
|
|
|
138
|
-
|
|
111
|
+
One model loop is not always enough. LittleGhost calls any unit that a caller can invoke like an Agent an **Assembly**.
|
|
139
112
|
|
|
140
|
-
|
|
141
|
-
- A `Swarm` lets configured agents choose direct handoffs to one another.
|
|
142
|
-
- A `Graph` follows named nodes and application-declared edges.
|
|
113
|
+
An Agent is the smallest Assembly. Workflow, Swarm, and Graph coordinate several participants while preserving the same entrypoints:
|
|
143
114
|
|
|
144
115
|
```ruby
|
|
145
|
-
CustomerSupportAgent.ask(
|
|
146
|
-
ResponseWorkflow.ask(
|
|
147
|
-
ProblemSolverSwarm.ask(
|
|
148
|
-
SupportFlowGraph.ask(
|
|
116
|
+
CustomerSupportAgent.ask(question)
|
|
117
|
+
ResponseWorkflow.ask(question)
|
|
118
|
+
ProblemSolverSwarm.ask(question)
|
|
119
|
+
SupportFlowGraph.ask(question)
|
|
149
120
|
```
|
|
150
121
|
|
|
151
|
-
|
|
122
|
+
That shared calling style is what makes composition feel natural. A controller, job, or CLI does not need to know whether one Agent answered or a whole support process worked together.
|
|
152
123
|
|
|
153
|
-
|
|
124
|
+
## Choose who controls the next step
|
|
154
125
|
|
|
155
|
-
|
|
126
|
+
The coordination types differ mainly in who decides what happens next:
|
|
156
127
|
|
|
157
|
-
|
|
128
|
+
| Need | Choose | Who controls the next step? |
|
|
129
|
+
| --- | --- | --- |
|
|
130
|
+
| One model-driven behavior | Agent | The active model loop |
|
|
131
|
+
| A model should delegate a named task | Subagent | The parent model |
|
|
132
|
+
| Ruby should enforce ordering or branching | Workflow | The workflow's Ruby code |
|
|
133
|
+
| Specialists should choose permitted handoffs | Swarm | The active agent |
|
|
134
|
+
| Allowed routes should be visible in advance | Graph | Declared nodes and edges |
|
|
158
135
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
model "customer_support.research"
|
|
163
|
-
system_prompt "Return a concise evidence summary."
|
|
164
|
-
end
|
|
136
|
+
### Subagents bring in a specialist
|
|
137
|
+
|
|
138
|
+
A **subagent** is a specialist that a parent Agent can call for help. The parent model chooses when to delegate, reads the result, and then continues its own answer.
|
|
165
139
|
|
|
140
|
+
```ruby
|
|
166
141
|
class CustomerSupportAgent < LittleGhost::Agent
|
|
167
|
-
model "
|
|
168
|
-
tools HelpCenterLookupTool
|
|
142
|
+
model "openrouter:openai/gpt-5.6-luna"
|
|
169
143
|
subagent ResearchAgent, kind: "research"
|
|
170
144
|
end
|
|
171
145
|
```
|
|
172
146
|
|
|
173
|
-
|
|
147
|
+
Use a subagent when delegation is part of one model's decision-making. Use a Workflow when application code must guarantee that a step happens.
|
|
174
148
|
|
|
175
|
-
|
|
149
|
+
### Workflows make Ruby the coordinator
|
|
176
150
|
|
|
177
|
-
|
|
151
|
+
A **Workflow** coordinates work with ordinary Ruby. Its `perform` method can call an Agent or another Assembly, read a result, choose a branch, or run independent steps together.
|
|
178
152
|
|
|
179
|
-
|
|
153
|
+
`invoke` prepares a lazy child call. Reading `.output` runs an intermediate child. Return the final `invoke` itself, without reading its output, so that answer can stream to the caller.
|
|
180
154
|
|
|
181
155
|
```ruby
|
|
182
156
|
class ResponseWorkflow < LittleGhost::Workflow
|
|
@@ -184,9 +158,7 @@ class ResponseWorkflow < LittleGhost::Workflow
|
|
|
184
158
|
|
|
185
159
|
def perform
|
|
186
160
|
research = invoke(ResearchAgent).output
|
|
187
|
-
|
|
188
161
|
invoke CustomerSupportAgent, input: <<~PROMPT
|
|
189
|
-
Customer request:
|
|
190
162
|
#{input.text}
|
|
191
163
|
|
|
192
164
|
Research:
|
|
@@ -196,157 +168,80 @@ class ResponseWorkflow < LittleGhost::Workflow
|
|
|
196
168
|
end
|
|
197
169
|
```
|
|
198
170
|
|
|
199
|
-
|
|
171
|
+
Workflow children receive the caller's history and application context by default. Pass `history: []`, `context: {}`, or redacted values when a participant should receive less.
|
|
200
172
|
|
|
201
|
-
|
|
173
|
+
### Swarms let agents hand work to one another
|
|
202
174
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
```ruby
|
|
206
|
-
research, verification = parallel(
|
|
207
|
-
invoke(ResearchGraph, as: :research),
|
|
208
|
-
invoke(VerificationWorkflow, as: :verification),
|
|
209
|
-
max_concurrency: 2
|
|
210
|
-
)
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
Results preserve declaration order. Each branch receives isolated application context and cooperative cancellation. `timeout:`, `retries:`, `retry_on:`, and `retry_delay:` apply to `invoke`; retries require explicit exception classes because rerunning an Assembly may repeat tool side effects.
|
|
214
|
-
|
|
215
|
-
Cancellation, deadlines, and step timeouts are cooperative. They do not forcibly stop provider or tool code, and they do not roll back external side effects. A participant must honor its cancellation token or deadline, and applications must decide whether an operation is safe to retry.
|
|
216
|
-
|
|
217
|
-
A workflow has the same entrypoint API as an agent:
|
|
218
|
-
|
|
219
|
-
```ruby
|
|
220
|
-
run = ResponseWorkflow.ask("Review this unusual refund request")
|
|
221
|
-
|
|
222
|
-
puts run.response
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
Choose a subagent when delegation is part of the model's judgment. Choose a workflow when ordering and branching are application invariants. They can coexist: `ResponseWorkflow` can always collect baseline research, while `CustomerSupportAgent` can still delegate a new question that arises while drafting the response.
|
|
226
|
-
|
|
227
|
-
## Swarms use direct agent handoffs
|
|
228
|
-
|
|
229
|
-
A swarm keeps one member active at a time and injects one reserved `handoff_to_agent` tool. A member either answers the caller or hands the request directly to another configured member:
|
|
175
|
+
A **Swarm** is a group of Agents that can hand work to one another. One member is active at a time. It can answer the caller or choose one of its allowed specialists.
|
|
230
176
|
|
|
231
177
|
```ruby
|
|
232
178
|
class ProblemSolverSwarm < LittleGhost::Swarm
|
|
233
179
|
member TriageAgent
|
|
234
180
|
member BillingAgent
|
|
235
181
|
member AccountAgent
|
|
182
|
+
|
|
236
183
|
start TriageAgent
|
|
237
184
|
handoff TriageAgent, to: [BillingAgent, AccountAgent]
|
|
238
|
-
max_steps 12
|
|
239
|
-
max_handoff_repeats 3
|
|
240
185
|
end
|
|
241
186
|
```
|
|
242
187
|
|
|
243
|
-
|
|
188
|
+
A Swarm is intentionally agent-to-agent. Its members are Agents, not other kinds of Assembly. Caller history and application context stay hidden unless a member opts in. Treat every handoff message as untrusted model input.
|
|
244
189
|
|
|
245
|
-
|
|
190
|
+
### Graphs make routes visible
|
|
246
191
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
A graph names Assembly nodes and directed edges. Ordinary edges select exactly one next node, while explicit forks and joins add bounded parallel work without shared mutable reducers:
|
|
192
|
+
A **Graph** connects named Assembly nodes with declared edges. Nodes can contain Agents, Workflows, Swarms, or other Graphs.
|
|
250
193
|
|
|
251
194
|
```ruby
|
|
252
195
|
class SupportFlowGraph < LittleGhost::Graph
|
|
253
196
|
node :triage, TriageAgent
|
|
254
|
-
node :
|
|
255
|
-
node :
|
|
197
|
+
node :billing, BillingAgent
|
|
198
|
+
node :general, CustomerSupportAgent
|
|
256
199
|
node :respond, CustomerSupportAgent
|
|
257
200
|
|
|
258
201
|
start :triage
|
|
259
|
-
|
|
260
|
-
|
|
202
|
+
edge :triage, :billing do |state|
|
|
203
|
+
state.result(:triage).output == "billing"
|
|
204
|
+
end
|
|
205
|
+
edge :triage, :general
|
|
206
|
+
edge :billing, :respond
|
|
207
|
+
edge :general, :respond
|
|
261
208
|
finish :respond
|
|
262
|
-
max_steps 12
|
|
263
209
|
end
|
|
264
210
|
```
|
|
265
211
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
By default, a downstream node receives the original multimodal input plus labeled predecessor output. A join receives every branch output in declaration order. Pass `input: ->(state) { ... }` on an edge, error edge, or join to replace that mapping. Nodes receive no caller history or application context unless their declaration opts in with `history: true` or `context: true`. The original request and routed outputs still cross node and provider boundaries by default, and routing callbacks can inspect the original context through `Graph::State`; map or redact inputs explicitly when participants have different privileges. Nodes may name any Assembly type, class, builder, or immutable definition. Fork branches may follow ordinary edges before reaching their distinct declared join sources.
|
|
212
|
+
Graph nodes do not receive caller history or application context unless they opt in. They still receive the original request and the outputs routed to them. Use edge input mappers to choose or redact what moves forward.
|
|
269
213
|
|
|
270
|
-
|
|
214
|
+
## Class definitions first, builders when needed
|
|
271
215
|
|
|
272
|
-
|
|
216
|
+
Named classes are the default way to organize reusable behavior. They are readable, load through normal Ruby conventions, and give the coordination style a visible name such as `ResponseWorkflow` or `SupportFlowGraph`.
|
|
273
217
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
Class definitions are the default because they keep behavior, names, and topology close together. Each agent or assembly class can produce an immutable `.definition` snapshot or an independent mutable `.to_builder` variant.
|
|
277
|
-
|
|
278
|
-
Use a builder when application code discovers participants or routes at runtime. The builder records the same declaration that the class DSL would organize:
|
|
218
|
+
Every assembly class can also produce a mutable builder:
|
|
279
219
|
|
|
280
220
|
```ruby
|
|
281
|
-
graph =
|
|
282
|
-
graph.node :
|
|
283
|
-
graph.
|
|
284
|
-
graph.
|
|
285
|
-
graph.edge :triage, :respond
|
|
286
|
-
graph.finish :respond
|
|
221
|
+
graph = SupportFlowGraph.to_builder
|
|
222
|
+
graph.node :audit, AuditAgent
|
|
223
|
+
graph.edge :respond, :audit
|
|
224
|
+
graph.finish :audit
|
|
287
225
|
graph.validate!
|
|
288
|
-
|
|
289
|
-
run = graph.ask("Can I get a refund?")
|
|
226
|
+
run = graph.ask("Review order 481")
|
|
290
227
|
```
|
|
291
228
|
|
|
292
|
-
|
|
229
|
+
Use a builder when trusted application configuration decides the participants or routes. Each run gets a fixed copy of the builder as it looked when the run began, so later edits affect later runs. Ruby callbacks still see any application objects they captured.
|
|
293
230
|
|
|
294
|
-
##
|
|
231
|
+
## One result, including the journey
|
|
295
232
|
|
|
296
|
-
|
|
233
|
+
Every assembly produces the same top-level `Run` and final `RunResult`. Composite assemblies also keep a size-limited record of the participants that ran:
|
|
297
234
|
|
|
298
235
|
```ruby
|
|
299
|
-
|
|
300
|
-
assembly_as_tool SupportFlowGraph, name: "investigate_support_case"
|
|
301
|
-
end
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
`assemblies_as_tools` declares several with shared options. Existing `agent_as_tool` and `agents_as_tools` remain agent-specific aliases. Composite assemblies do not accept agent-only model or tool overrides.
|
|
305
|
-
|
|
306
|
-
An assembly tool receives the invoking tool context's application state on every call. `preserve_context: false` prevents conversational history from carrying between calls; it does not suppress that application state. Nested tools must continue to authorize privileged work from trusted context rather than from model-authored input.
|
|
307
|
-
|
|
308
|
-
## Structured results separate data from prose
|
|
236
|
+
run = SupportFlowGraph.ask("Why was I charged twice?")
|
|
309
237
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
class ResearchAgent < LittleGhost::Agent
|
|
314
|
-
model "customer_support.research"
|
|
315
|
-
result_schema(
|
|
316
|
-
{
|
|
317
|
-
type: "object",
|
|
318
|
-
properties: {
|
|
319
|
-
summary: {type: "string"},
|
|
320
|
-
sources: {type: "array", items: {type: "string"}}
|
|
321
|
-
},
|
|
322
|
-
required: %w[summary sources],
|
|
323
|
-
additionalProperties: false
|
|
324
|
-
},
|
|
325
|
-
name: "support_research"
|
|
326
|
-
)
|
|
327
|
-
end
|
|
238
|
+
run.response
|
|
239
|
+
run.result.steps
|
|
240
|
+
run.result.trajectory.transitions
|
|
328
241
|
```
|
|
329
242
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
Use structured results when code consumes fields. Keep ordinary text when a human is the final consumer.
|
|
333
|
-
|
|
334
|
-
## Sessions preserve conversation, streams expose progress
|
|
335
|
-
|
|
336
|
-
The default session store is in-memory. A configured `SessionStore` can load history and state before an agent runs and checkpoint coherent turns as work progresses. The application must supply stable session and actor identifiers when it wants continuity and isolation.
|
|
337
|
-
|
|
338
|
-
Applications that need to reconcile persisted messages with invocation history can register a `LittleGhost::Runtime::Hook` and implement `session_history`. The hook receives the run plus `stored:` and `fallback:` message collections. Return the history to use, or `nil` to defer to the next hook and ultimately the session default. This keeps application-specific reconciliation policy outside the framework session type.
|
|
339
|
-
|
|
340
|
-
Streams expose generic framework events rather than provider wire formats. Consumers can render text deltas, observe tool or subagent activity, collect usage, and react to terminal outcomes without coupling to OpenAI, OpenRouter, or Bedrock. The optional AG-UI adapter translates the same events at an interface boundary.
|
|
341
|
-
|
|
342
|
-
## Keep the boundary visible
|
|
343
|
-
|
|
344
|
-
The core design can be summarized as five choices:
|
|
243
|
+
This shows callers which participants ran without including raw provider responses. A Swarm or Graph may hide intermediate model events from the public stream so the response stays coherent. That is a presentation choice, not a privacy boundary: routed outputs and step summaries still exist.
|
|
345
244
|
|
|
346
|
-
|
|
347
|
-
- Put model behavior and available capabilities on agent classes.
|
|
348
|
-
- Put privileged application operations behind narrow, authorized tools.
|
|
349
|
-
- Put imperative ordering in workflows, dynamic peer routing in swarms, and guided routing in graphs.
|
|
350
|
-
- Leave addressable background delegation to subagents.
|
|
245
|
+
The pieces now fit together: Agents define behavior. Tools connect them to Ruby. Runs record one execution. Assemblies let the system grow without changing the caller.
|
|
351
246
|
|
|
352
|
-
|
|
247
|
+
Continue with [Compose Agents](assemblies.md) to put several agents to work together. If you are ready to connect the feature to a real application, jump to [Running in Production](production.md).
|