little_ghost 0.4.0 → 0.5.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 +28 -6
- data/docs/guides/assemblies.md +143 -17
- data/docs/guides/code_mode.md +275 -0
- data/docs/guides/core_concepts.md +46 -30
- data/docs/guides/getting_started.md +17 -6
- data/docs/guides/integrations.md +217 -0
- data/docs/guides/models_and_providers.md +125 -0
- data/docs/guides/production.md +81 -15
- data/docs/guides/prompt_views.md +14 -7
- data/docs/guides/sandboxing.md +282 -0
- data/docs/guides/skills.md +141 -0
- data/docs/guides/structured_outputs_and_content.md +135 -0
- data/docs/guides/tools.md +329 -0
- data/lib/little_ghost/ag_ui/adapter.rb +2 -2
- data/lib/little_ghost/agent/delegation.rb +2 -2
- data/lib/little_ghost/agent/skills.rb +6 -1
- data/lib/little_ghost/agent/tool_loop.rb +6 -2
- data/lib/little_ghost/agent.rb +248 -46
- data/lib/little_ghost/agent_builder.rb +61 -22
- data/lib/little_ghost/agent_stream_source.rb +262 -0
- data/lib/little_ghost/artifact.rb +182 -0
- data/lib/little_ghost/artifacts/presentation_budget.rb +44 -0
- data/lib/little_ghost/artifacts/workspace_store.rb +370 -0
- data/lib/little_ghost/artifacts.rb +11 -0
- data/lib/little_ghost/assembly.rb +63 -17
- data/lib/little_ghost/assembly_builder.rb +26 -26
- data/lib/little_ghost/assembly_execution.rb +23 -2
- data/lib/little_ghost/code_mode/broker.rb +164 -0
- data/lib/little_ghost/code_mode/catalog.rb +54 -0
- data/lib/little_ghost/code_mode/engine.rb +58 -0
- data/lib/little_ghost/code_mode/javascript/catalog.rb +118 -0
- data/lib/little_ghost/code_mode/javascript/client.rb +550 -0
- data/lib/little_ghost/code_mode/javascript/host.rb +347 -0
- data/lib/little_ghost/code_mode/javascript/session.rb +579 -0
- data/lib/little_ghost/code_mode/javascript_engine.rb +172 -0
- data/lib/little_ghost/code_mode/protocol.rb +78 -0
- data/lib/little_ghost/code_mode/ruby/catalog.rb +80 -0
- data/lib/little_ghost/code_mode/ruby/host.rb +102 -0
- data/lib/little_ghost/code_mode/ruby/session.rb +508 -0
- data/lib/little_ghost/code_mode/ruby_engine.rb +86 -0
- data/lib/little_ghost/code_mode/runtime.rb +335 -0
- data/lib/little_ghost/code_mode/session.rb +61 -0
- data/lib/little_ghost/code_mode/types.rb +58 -0
- data/lib/little_ghost/code_mode.rb +53 -0
- data/lib/little_ghost/configuration.rb +107 -12
- data/lib/little_ghost/content.rb +19 -8
- data/lib/little_ghost/data_map.rb +2 -2
- data/lib/little_ghost/errors.rb +26 -7
- data/lib/little_ghost/execution.rb +1 -2
- data/lib/little_ghost/graph.rb +367 -207
- data/lib/little_ghost/mcp/client.rb +487 -88
- data/lib/little_ghost/mcp/toolset.rb +210 -0
- data/lib/little_ghost/mcp/types.rb +216 -0
- data/lib/little_ghost/mcp.rb +3 -0
- data/lib/little_ghost/model_capabilities.rb +9 -6
- data/lib/little_ghost/model_request.rb +0 -12
- data/lib/little_ghost/model_resolver.rb +13 -3
- data/lib/little_ghost/model_response.rb +3 -7
- data/lib/little_ghost/network/authorizer_server.rb +162 -0
- data/lib/little_ghost/network/certificate_authority.rb +98 -0
- data/lib/little_ghost/network/envoy_config.rb +362 -0
- data/lib/little_ghost/network/envoy_gateway.rb +409 -0
- data/lib/little_ghost/network/external_gateway.rb +68 -0
- data/lib/little_ghost/network.rb +96 -0
- data/lib/little_ghost/prompt_resolver.rb +7 -7
- data/lib/little_ghost/provider_registry.rb +3 -3
- data/lib/little_ghost/providers/anthropic.rb +8 -1
- data/lib/little_ghost/providers/gemini.rb +10 -1
- data/lib/little_ghost/providers/vertex_ai.rb +6 -1
- data/lib/little_ghost/run.rb +83 -13
- data/lib/little_ghost/run_context.rb +9 -0
- data/lib/little_ghost/run_result.rb +0 -7
- data/lib/little_ghost/runtime/hook.rb +5 -0
- data/lib/little_ghost/runtime/hooks/artifacts.rb +338 -0
- data/lib/little_ghost/runtime.rb +102 -34
- data/lib/little_ghost/sandbox/capabilities.rb +68 -0
- data/lib/little_ghost/sandbox/environment_policy.rb +41 -0
- data/lib/little_ghost/sandbox/filesystem.rb +377 -0
- data/lib/little_ghost/sandbox/isolated_backend.rb +163 -0
- data/lib/little_ghost/sandbox/limits.rb +48 -0
- data/lib/little_ghost/sandbox/mount.rb +129 -0
- data/lib/little_ghost/sandbox/network_policy.rb +108 -0
- data/lib/little_ghost/sandbox/policy.rb +88 -0
- data/lib/little_ghost/sandbox/process_runner.rb +103 -0
- data/lib/little_ghost/sandbox/process_session.rb +335 -0
- data/lib/little_ghost/sandbox/scope.rb +304 -0
- data/lib/little_ghost/sandbox.rb +158 -32
- data/lib/little_ghost/sandboxes/bubblewrap.rb +351 -0
- data/lib/little_ghost/sandboxes/native.rb +51 -0
- data/lib/little_ghost/sandboxes/seatbelt.rb +261 -0
- data/lib/little_ghost/sandboxes/unrestricted.rb +241 -0
- data/lib/little_ghost/session.rb +30 -6
- data/lib/little_ghost/skills/catalog.rb +94 -14
- data/lib/little_ghost/skills/resource_root.rb +42 -0
- data/lib/little_ghost/skills/skill.rb +0 -3
- data/lib/little_ghost/stream_event.rb +8 -13
- data/lib/little_ghost/subagents/control_tool.rb +8 -0
- data/lib/little_ghost/subagents/manager.rb +12 -9
- data/lib/little_ghost/support/callbacks.rb +3 -1
- data/lib/little_ghost/support/content_capture.rb +3 -3
- data/lib/little_ghost/support/http_client.rb +2 -2
- data/lib/little_ghost/support/redactor.rb +1 -1
- data/lib/little_ghost/tool.rb +104 -53
- data/lib/little_ghost/tool_registry.rb +1 -1
- data/lib/little_ghost/tools/filesystem.rb +1 -1
- data/lib/little_ghost/tools/shell.rb +4 -3
- data/lib/little_ghost/version.rb +1 -1
- data/lib/little_ghost/workspace.rb +222 -8
- data/lib/little_ghost.rb +21 -12
- metadata +98 -2
- data/lib/little_ghost/unrestricted_sandbox.rb +0 -306
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c6ec454a6085af7b0e2519eb0137e2f70115f0c00197d9f1914ac336c9fe4810
|
|
4
|
+
data.tar.gz: bde8feed25fc76a6e0290ec8462b12eb5b2df90c368214850caaa705a3da75d2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0c7081dc35068d5e3e3e0d917e4f024657cbecc117a0952b4ccfa329a9a4be2de77a96c87d9d7cf1eb3f9092219ed108d07422fa501e427542fdfb400aaf3e4a
|
|
7
|
+
data.tar.gz: 939d5fac29d8a1607225185f09784e2fe014d7ef20c20ba856ebda0b30c8e6634a79a9ff94725287f924576caecb2080204dec5fa7b36af3b801d02c3d9de93c
|
data/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Build AI features that feel at home in Ruby
|
|
2
2
|
|
|
3
|
+
> **Using a coding agent?** Start with
|
|
4
|
+
> [`llms.txt`](https://mattyr.github.io/little_ghost/llms.txt) for a concise map
|
|
5
|
+
> of the guides and API. [`llms-full.txt`](https://mattyr.github.io/little_ghost/llms-full.txt)
|
|
6
|
+
> contains the complete documentation in one file.
|
|
7
|
+
|
|
3
8
|
LittleGhost is a Ruby library for building AI features with agents and composable assemblies. With `OPENROUTER_API_KEY` set, start with one class, give it a prompt, and call it like the rest of your application code:
|
|
4
9
|
|
|
5
10
|
```ruby
|
|
@@ -17,7 +22,10 @@ run.response
|
|
|
17
22
|
|
|
18
23
|
That small definition is already a complete agent. LittleGhost makes the model call, tracks usage, supports streaming, and closes the resources it creates for the request. Add a tool when the agent needs something from your application. Bring in more agents when the work grows.
|
|
19
24
|
|
|
20
|
-
Model requests may send system instructions, caller input, conversation history,
|
|
25
|
+
Model requests may send system instructions, caller input, conversation history,
|
|
26
|
+
Tool results, and attachments to the selected provider. Model wording can vary
|
|
27
|
+
between runs. [Models and Providers](docs/guides/models_and_providers.md) explains
|
|
28
|
+
how to choose where each Agent sends its requests.
|
|
21
29
|
|
|
22
30
|
## Install the gem
|
|
23
31
|
|
|
@@ -63,7 +71,13 @@ class CustomerSupportAgent < LittleGhost::Agent
|
|
|
63
71
|
end
|
|
64
72
|
```
|
|
65
73
|
|
|
66
|
-
The schema checks the shape of the input. Your Ruby code still decides whether
|
|
74
|
+
The schema checks the shape of the input. Your Ruby code still decides whether
|
|
75
|
+
the operation is allowed. The result goes back to the model as context.
|
|
76
|
+
|
|
77
|
+
An ordinary Tool runs in your Ruby process. When a Tool needs files or child
|
|
78
|
+
processes, it can delegate that work through a Sandbox. Code mode goes one step
|
|
79
|
+
further: a sandboxed interpreter can compose several Tools, while every Tool
|
|
80
|
+
call still returns to your Ruby Tool for validation and permission checks.
|
|
67
81
|
|
|
68
82
|
## Grow without changing the caller
|
|
69
83
|
|
|
@@ -99,16 +113,24 @@ The result stays familiar too. Every call returns a `Run` with the response,
|
|
|
99
113
|
outcome, usage, and any final error. A coordinated assembly also records which
|
|
100
114
|
participants ran. Use `.stream_ask` to watch the work as it happens.
|
|
101
115
|
|
|
102
|
-
|
|
116
|
+
LittleGhost is pre-1.0. Pin the gem version and review release notes before
|
|
117
|
+
upgrading, because interfaces may change between releases.
|
|
103
118
|
|
|
104
119
|
## Keep going
|
|
105
120
|
|
|
106
121
|
- [Getting Started](docs/guides/getting_started.md) takes you from installation to a tool-backed, streaming agent.
|
|
107
122
|
- [Core Concepts](docs/guides/core_concepts.md) builds the mental model from Agent to Assembly.
|
|
108
|
-
- [
|
|
123
|
+
- [Models and Providers](docs/guides/models_and_providers.md) gives shared model choices application-facing names.
|
|
109
124
|
- [Prompts as Views](docs/guides/prompt_views.md) gives growing instructions, shared pieces, and application values a natural home.
|
|
110
|
-
- [
|
|
111
|
-
- [
|
|
125
|
+
- [Tools](docs/guides/tools.md) explains how models call focused Ruby operations.
|
|
126
|
+
- [Structured Results and Content](docs/guides/structured_outputs_and_content.md) covers checked result shapes, images, and documents.
|
|
127
|
+
- [Compose Agents](docs/guides/assemblies.md) walks through workflows, swarms, graphs, nesting, and builders.
|
|
128
|
+
- [Skills](docs/guides/skills.md) organizes reusable instructions and supporting resources.
|
|
129
|
+
- [Workspaces and Sandboxes](docs/guides/sandboxing.md) gives files and child processes a deliberate place to run.
|
|
130
|
+
- [Code Mode](docs/guides/code_mode.md) lets a model compose Tools in sandboxed Ruby or optional JavaScript.
|
|
131
|
+
- [Integrations](docs/guides/integrations.md) connects MCP, AG-UI, and OpenTelemetry.
|
|
132
|
+
- [Running in Production](docs/guides/production.md) covers configuration, saved conversations, supervision, and observability.
|
|
133
|
+
- [API reference](rdoc-ref:LittleGhost) provides exact method signatures and ownership rules.
|
|
112
134
|
|
|
113
135
|
### For contributors
|
|
114
136
|
|
data/docs/guides/assemblies.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
An Assembly lets several participants answer through the same familiar calls as one Agent. This guide grows the customer-support example through each coordination style, then shows how to nest and construct assemblies dynamically.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Call every assembly the same way
|
|
6
6
|
|
|
7
7
|
Callers do not need a branch for each implementation:
|
|
8
8
|
|
|
@@ -136,7 +136,9 @@ Opt in only for a member that needs the data:
|
|
|
136
136
|
member AccountAgent, history: true, context: true
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
-
Intermediate model text stays out of the caller-facing stream, leaving one
|
|
139
|
+
Intermediate model text stays out of the caller-facing stream, leaving one
|
|
140
|
+
coherent public answer. The next member still receives the handoff, and the
|
|
141
|
+
result keeps a bounded summary of the journey.
|
|
140
142
|
|
|
141
143
|
## Use a Graph for guided routes
|
|
142
144
|
|
|
@@ -163,19 +165,19 @@ end
|
|
|
163
165
|
SupportFlowGraph.validate!
|
|
164
166
|
```
|
|
165
167
|
|
|
166
|
-
Conditions and input mappers read an immutable `Graph::State`. At most one conditional
|
|
168
|
+
Conditions and input mappers read an immutable `Graph::State`. At most one conditional route may match. If several match, LittleGhost raises `AssemblyRoutingError` instead of guessing which one wins. One unconditional edge can catch the request when none match.
|
|
167
169
|
|
|
168
|
-
Graph nodes start without caller history or application context.
|
|
170
|
+
Graph nodes start without caller history or application context. The start node receives the original input. By default, each downstream node receives the original task plus its immediate predecessor results. Use an input mapper to replace or redact that data before it moves to a provider or participant that should see less.
|
|
169
171
|
|
|
170
|
-
Opt in
|
|
172
|
+
Opt in when a node needs caller context:
|
|
171
173
|
|
|
172
174
|
```ruby
|
|
173
175
|
node :account_lookup, AccountLookupAgent, context: true
|
|
174
176
|
```
|
|
175
177
|
|
|
176
|
-
###
|
|
178
|
+
### Run bounded parallel paths
|
|
177
179
|
|
|
178
|
-
|
|
180
|
+
Give one node several unconditional edges when its result should start independent branches. LittleGhost finds their first unambiguous common successor and waits for every branch before running it:
|
|
179
181
|
|
|
180
182
|
```ruby
|
|
181
183
|
class InvestigationGraph < LittleGhost::Graph
|
|
@@ -185,16 +187,88 @@ class InvestigationGraph < LittleGhost::Graph
|
|
|
185
187
|
node :respond, CustomerSupportAgent
|
|
186
188
|
|
|
187
189
|
start :triage
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
input: ->(state) { state.branch_results.transform_values(&:output) }
|
|
193
|
-
)
|
|
190
|
+
edge :triage, :ledger
|
|
191
|
+
edge :triage, :policy
|
|
192
|
+
edge :ledger, :respond
|
|
193
|
+
edge :policy, :respond
|
|
194
194
|
finish :respond
|
|
195
195
|
end
|
|
196
196
|
```
|
|
197
197
|
|
|
198
|
+
Set `max_concurrency` on the Graph to bound every parallel group. An edge with
|
|
199
|
+
an array target can declare the group explicitly and override that bound:
|
|
200
|
+
|
|
201
|
+
```ruby
|
|
202
|
+
max_concurrency 4
|
|
203
|
+
edge :triage, [:ledger, :policy], max_concurrency: 2
|
|
204
|
+
edge [:ledger, :policy], :respond
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
#### Join parallel branches
|
|
208
|
+
|
|
209
|
+
An array source declares a wait-for-all convergence. Use it when the common
|
|
210
|
+
successor cannot be inferred or when the convergence needs its own input
|
|
211
|
+
mapper. Parallel groups cannot nest. `validate!` raises `ConfigurationError`
|
|
212
|
+
when inference has no single convergence, finds competing routes at a branch
|
|
213
|
+
boundary, or encounters overlapping or nested groups.
|
|
214
|
+
|
|
215
|
+
The first nodes in a parallel group receive the original task and the source
|
|
216
|
+
result. The convergence target receives the original task and each immediate
|
|
217
|
+
predecessor result in declaration order. LittleGhost labels them as context:
|
|
218
|
+
|
|
219
|
+
```text
|
|
220
|
+
Original Task:
|
|
221
|
+
Why is transfer 481 pending?
|
|
222
|
+
|
|
223
|
+
Inputs from previous nodes:
|
|
224
|
+
|
|
225
|
+
From ledger:
|
|
226
|
+
The ledger entry is awaiting settlement.
|
|
227
|
+
|
|
228
|
+
From policy:
|
|
229
|
+
Pending transfers usually settle within two business days.
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
#### Map inputs between nodes
|
|
233
|
+
|
|
234
|
+
An `input` mapper replaces this default with the exact value returned by the
|
|
235
|
+
mapper. Put it on an edge to control one transition, or on a node to control
|
|
236
|
+
every route into that target. A selected edge or edge-group mapper takes
|
|
237
|
+
precedence over the target node mapper:
|
|
238
|
+
|
|
239
|
+
```ruby
|
|
240
|
+
edge :triage, :ledger, input: lambda { |state|
|
|
241
|
+
"Investigate this transfer:\n#{state.result(:triage).output}"
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Conditions and mappers receive a copied, frozen `Graph::State`, so they cannot
|
|
246
|
+
change the running Graph.
|
|
247
|
+
Use `state.input` for the original request, `state.results` for completed nodes,
|
|
248
|
+
and `state.incoming_results` for the immediate predecessors. The
|
|
249
|
+
`LittleGhost::Graph::State` API reference lists every routing value.
|
|
250
|
+
|
|
251
|
+
Conditions and mappers are application code. Their state includes copies of
|
|
252
|
+
caller history and application context, even when the destination node does not
|
|
253
|
+
receive those values.
|
|
254
|
+
|
|
255
|
+
Use an explicit array-source edge when a fan-in needs one mapper:
|
|
256
|
+
|
|
257
|
+
```ruby
|
|
258
|
+
edge [:ledger, :policy], :respond, input: lambda { |state|
|
|
259
|
+
JSON.generate(state.incoming_results.transform_values(&:output))
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
#### Control data crossing branches
|
|
264
|
+
|
|
265
|
+
By default, the original request and complete source output go to every
|
|
266
|
+
parallel branch. An input mapper can replace the branch input, and a redaction
|
|
267
|
+
assembly before the fan-out can narrow the source output. Use those options
|
|
268
|
+
when a participant or provider should receive only part of the data.
|
|
269
|
+
|
|
270
|
+
#### Recover and review
|
|
271
|
+
|
|
198
272
|
An error edge can send an expected failure to a recovery Assembly. Call `validate!` before the first run.
|
|
199
273
|
|
|
200
274
|
Once the topology grows, `InvestigationGraph.to_mermaid` returns Mermaid diagram source for the routes you declared. Render it in a Mermaid-aware editor or documentation page when a picture makes the graph easier to review.
|
|
@@ -217,6 +291,50 @@ A timeout asks the running code to stop; it cannot forcibly end arbitrary Ruby o
|
|
|
217
291
|
|
|
218
292
|
Retries start at zero. When `retries` is greater than zero, `retry_on` must list the exception classes that are safe to try again. LittleGhost does not retry every failure by default.
|
|
219
293
|
|
|
294
|
+
## Watch every agent in an assembly
|
|
295
|
+
|
|
296
|
+
Follow each participant while a composite assembly runs by handling its contextual `:agent_stream` events. These events arrive alongside the coherent public answer and assembly lifecycle events:
|
|
297
|
+
|
|
298
|
+
```ruby
|
|
299
|
+
stream = SupportFlowGraph.stream_ask("Why was I charged twice?")
|
|
300
|
+
|
|
301
|
+
run = stream.each do |event|
|
|
302
|
+
next unless event.type == :agent_stream
|
|
303
|
+
|
|
304
|
+
source = event.data.fetch(:source)
|
|
305
|
+
agent_event = event.data.fetch(:event)
|
|
306
|
+
participant = source.assembly_path.last&.participant || source.agent_id
|
|
307
|
+
|
|
308
|
+
case agent_event.type
|
|
309
|
+
when :invocation_start
|
|
310
|
+
routed_input = event.data.fetch(:input)
|
|
311
|
+
render_input(participant, routed_input)
|
|
312
|
+
when :text_delta
|
|
313
|
+
publish_progress(participant, agent_event.data.fetch(:text))
|
|
314
|
+
when :invocation_stop
|
|
315
|
+
record_result(participant, agent_event.data.fetch(:result))
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
run.completed? # => true
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
`source.agent_id` identifies the Agent class, `source.agent_path` distinguishes managed subagents, and `source.operation_id` groups one invocation. `source.assembly_path` lists the enclosing Workflow, Swarm, or Graph steps from the outside inward.
|
|
323
|
+
|
|
324
|
+
The routed input and inner event are copied and frozen before they reach the
|
|
325
|
+
observer, so changing an event can't affect the running assembly. Parallel
|
|
326
|
+
participants can interleave. Events from each Agent retain their order, and
|
|
327
|
+
LittleGhost never calls the stream block concurrently.
|
|
328
|
+
|
|
329
|
+
The contextual wrapper arrives before the corresponding ordinary event. An assembly's final Agent therefore appears through both projections. Filter for `:agent_stream` when building an all-agent view, or handle ordinary events when rendering only the final answer. Pass `include_agent_events: false` when a composite assembly caller only wants the ordinary public stream. Standalone Agent streams keep their ordinary events by default and accept `include_agent_events: true` when source metadata is useful.
|
|
330
|
+
|
|
331
|
+
The AG-UI adapter ignores contextual wrappers. Translate them explicitly if an AG-UI client should receive participant activity.
|
|
332
|
+
|
|
333
|
+
> **Safety note:** A composite stream can include inputs, reasoning, Tool
|
|
334
|
+
> arguments and results, errors, and output from every participant. Check that
|
|
335
|
+
> the destination may see the complete Run, or filter the events before sending
|
|
336
|
+
> or storing them.
|
|
337
|
+
|
|
220
338
|
## Inspect what the assembly did
|
|
221
339
|
|
|
222
340
|
A composite result remembers the steps it took. `trajectory` lets you explore them:
|
|
@@ -232,7 +350,8 @@ policy = trajectory.find { |step| step.participant == "policy" }
|
|
|
232
350
|
trajectory.concurrent?(ledger.id, policy.id)
|
|
233
351
|
```
|
|
234
352
|
|
|
235
|
-
Step outputs and buffered events have size limits. Use your application's
|
|
353
|
+
Step outputs and buffered events have size limits. Use your application's
|
|
354
|
+
instrumentation when you need deeper diagnostics.
|
|
236
355
|
|
|
237
356
|
## Compose assemblies inside assemblies
|
|
238
357
|
|
|
@@ -259,11 +378,17 @@ class SupportCoordinatorAgent < LittleGhost::Agent
|
|
|
259
378
|
end
|
|
260
379
|
```
|
|
261
380
|
|
|
262
|
-
The nested assembly receives the parent Tool's current working state. That state
|
|
381
|
+
The nested assembly receives the parent Tool's current working state. That state
|
|
382
|
+
may include values restored from a Session. `preserve_context` controls
|
|
383
|
+
conversation history only: when it is false, working state still passes to the
|
|
384
|
+
nested assembly. A nested Tool that reads private data or performs a write
|
|
385
|
+
should check values established for the current request or checked again after
|
|
386
|
+
loading.
|
|
263
387
|
|
|
264
388
|
## Reach for builders when definitions are dynamic
|
|
265
389
|
|
|
266
|
-
Classes are the preferred form in application code. Use a builder when
|
|
390
|
+
Classes are the preferred form in application code. Use a builder when runtime
|
|
391
|
+
configuration decides the nodes or routes:
|
|
267
392
|
|
|
268
393
|
```ruby
|
|
269
394
|
graph = LittleGhost::GraphBuilder.new(
|
|
@@ -283,4 +408,5 @@ run = graph.ask("Where is my order?")
|
|
|
283
408
|
|
|
284
409
|
Each builder uses the same declarations as its matching class. The builder stays editable, but each run gets a fixed copy of its current definition. Later edits affect later runs. Ruby callbacks still see any application objects they captured.
|
|
285
410
|
|
|
286
|
-
Continue with [
|
|
411
|
+
Continue with [Skills](skills.md) when an Agent should discover focused
|
|
412
|
+
instructions and supporting resources only when a task needs them.
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# Code Mode
|
|
2
|
+
|
|
3
|
+
Code mode lets an Agent solve a multi-step Tool task in one small program. The
|
|
4
|
+
model can gather independent results, filter them, and combine them before it
|
|
5
|
+
returns to the conversation. Your Ruby Tools keep their usual permission
|
|
6
|
+
checks.
|
|
7
|
+
|
|
8
|
+
Start by adding code mode to an Agent that already has a Tool:
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
class HelpCenterLookupTool < LittleGhost::Tool
|
|
12
|
+
tool_name "help_center_lookup"
|
|
13
|
+
description "Find a support answer by topic."
|
|
14
|
+
input_schema(
|
|
15
|
+
type: "object",
|
|
16
|
+
properties: {query: {type: "string"}},
|
|
17
|
+
required: ["query"],
|
|
18
|
+
additionalProperties: false
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
def call(input)
|
|
22
|
+
entries = {
|
|
23
|
+
"refund policy" => "Refunds are available within 30 days.",
|
|
24
|
+
"shipping policy" => "Standard shipping takes three to five days."
|
|
25
|
+
}
|
|
26
|
+
entries.fetch(input.fetch("query"), "No matching entry.")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class ResearchAgent < LittleGhost::Agent
|
|
31
|
+
tools HelpCenterLookupTool
|
|
32
|
+
code_mode
|
|
33
|
+
end
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The language adapter that runs the program is called an engine. By default,
|
|
37
|
+
LittleGhost uses its Ruby engine and the native Sandbox for the host operating
|
|
38
|
+
system. It fails closed when that Sandbox is unavailable.
|
|
39
|
+
|
|
40
|
+
Code mode adds three control Tools to the conversation. `exec` starts a
|
|
41
|
+
program, `wait` checks on a program that is still working, and `stop` ends work
|
|
42
|
+
that is no longer needed. The model can now send Ruby like this to `exec`:
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
results = tools.parallel(
|
|
46
|
+
-> { tools.help_center_lookup(query: "refund policy") },
|
|
47
|
+
-> { tools.help_center_lookup(query: "shipping policy") }
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
text(results.join("\n"))
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
LittleGhost turns the Agent's Tool schemas into Ruby method signatures. The
|
|
54
|
+
model sees those signatures and the available Tool names in its instructions.
|
|
55
|
+
Those names, descriptions, and signatures form the code-mode Tool catalog. The
|
|
56
|
+
model can compose the results with ordinary Ruby values instead of guessing
|
|
57
|
+
how to call each Tool.
|
|
58
|
+
|
|
59
|
+
## See what runs where
|
|
60
|
+
|
|
61
|
+
The program runs in a child interpreter. The Tools do not.
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
model
|
|
65
|
+
│ writes a program
|
|
66
|
+
▼
|
|
67
|
+
exec ──> sandboxed Ruby process
|
|
68
|
+
│ tools.help_center_lookup(...)
|
|
69
|
+
▼
|
|
70
|
+
Tool broker in the parent Ruby process
|
|
71
|
+
│ normal Tool execution
|
|
72
|
+
▼
|
|
73
|
+
HelpCenterLookupTool#call
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The Tool broker receives interpreter calls in the parent Ruby process. It
|
|
77
|
+
accepts only Tools registered on the Agent, then sends each call through the
|
|
78
|
+
same validation, permission checks, limits, callbacks, events, and tracing used by
|
|
79
|
+
a direct Tool call.
|
|
80
|
+
|
|
81
|
+
Public streams show the brokered Tools by name. They omit the `exec`, `wait`,
|
|
82
|
+
and `stop` bookkeeping. Traces still record the control operation around its
|
|
83
|
+
nested Tool calls, so you can follow the complete execution.
|
|
84
|
+
|
|
85
|
+
Code mode does not change what a Tool can do. A Tool still runs as application
|
|
86
|
+
code, while the generated program runs in the configured Sandbox. Read
|
|
87
|
+
[Tools](tools.md) for Tool permission checks and [Workspaces and
|
|
88
|
+
Sandboxes](sandboxing.md) before giving generated programs file or process
|
|
89
|
+
access.
|
|
90
|
+
|
|
91
|
+
## Compose Tool calls with Ruby
|
|
92
|
+
|
|
93
|
+
Each `exec` starts a fresh Ruby process. Local variables, constants, and globals
|
|
94
|
+
do not carry into a later `exec`. Within one program, the model can use:
|
|
95
|
+
|
|
96
|
+
- Named methods such as `tools.help_center_lookup(query: "refund policy")`.
|
|
97
|
+
- `tools.call(name, arguments)` when the Tool name is dynamic.
|
|
98
|
+
- `tools.parallel` for independent calls whose results should preserve input
|
|
99
|
+
order.
|
|
100
|
+
- `ALL_TOOLS` to inspect the complete runtime catalog.
|
|
101
|
+
- `text(value)` to add user-visible output.
|
|
102
|
+
- The program's final expression as the completed value returned by `exec` or
|
|
103
|
+
a later `wait`.
|
|
104
|
+
- `finish(value)` to complete early.
|
|
105
|
+
|
|
106
|
+
The dynamic form accepts either the catalog name (`"help_center_lookup"`) or
|
|
107
|
+
the matching method name (`"tools.help_center_lookup"`).
|
|
108
|
+
|
|
109
|
+
JSON Tool results arrive as ordinary Ruby hashes, arrays, strings, numbers,
|
|
110
|
+
booleans, or `nil`. When a Tool returns `Tool::Result`, code mode uses its
|
|
111
|
+
Ruby `value`. Artifact bytes are not copied into program variables; the
|
|
112
|
+
artifacts return to the model once with the surrounding `exec` or `wait`
|
|
113
|
+
result. Stored references appear only when native media delivery exceeds its
|
|
114
|
+
limit. A Tool failure raises inside the program so its Ruby code can handle the
|
|
115
|
+
failure or return an error.
|
|
116
|
+
|
|
117
|
+
Fresh processes keep interpreter state from leaking across programs. Each Ruby
|
|
118
|
+
program also gets a temporary Workspace. LittleGhost removes it when the
|
|
119
|
+
program ends, so files created directly by the interpreter do not carry into a
|
|
120
|
+
later `exec`.
|
|
121
|
+
|
|
122
|
+
A brokered filesystem Tool uses the Agent Run's separate Workspace. Files
|
|
123
|
+
written through that Tool follow the Run Workspace's cleanup rules and may
|
|
124
|
+
persist.
|
|
125
|
+
|
|
126
|
+
## Check on work that takes longer
|
|
127
|
+
|
|
128
|
+
Most programs finish while `exec` is watching them, so their result is ready in
|
|
129
|
+
the same Tool call. If a program is still active after one minute, `exec`
|
|
130
|
+
returns `still_working`. The program keeps running. The model can call `wait`
|
|
131
|
+
to watch for up to another minute or `stop` when it no longer needs the result.
|
|
132
|
+
|
|
133
|
+
Both `exec` and `wait` return as soon as the program finishes. The one-minute
|
|
134
|
+
window is a maximum observation time, not a delay added to every call.
|
|
135
|
+
|
|
136
|
+
`wait` does not resume, restart, or extend the program. It returns only output
|
|
137
|
+
produced since the previous `exec` or `wait`. The returned status tells the
|
|
138
|
+
model what to do next:
|
|
139
|
+
|
|
140
|
+
- `still_working` means the program is active. Call `wait` again when its result
|
|
141
|
+
is still needed, or call `stop` to end it.
|
|
142
|
+
- `completed`, `error`, and `terminated` are final. There is no program to wait
|
|
143
|
+
for after one of these statuses.
|
|
144
|
+
|
|
145
|
+
The built-in engines give each program a total lifetime of one hour by default.
|
|
146
|
+
That deadline begins at `exec` and does not reset when the model calls `wait`.
|
|
147
|
+
The engine ends and cleans up an expired program even if the model never checks
|
|
148
|
+
on it again. Applications can configure a shorter total lifetime with
|
|
149
|
+
`wall_seconds`; the one-minute observation window remains fixed.
|
|
150
|
+
|
|
151
|
+
A code-mode session owns the engine's active child process and related
|
|
152
|
+
resources. It accepts only one `exec`, `wait`, or `stop` operation at a time.
|
|
153
|
+
The Agent closes the session when its current call ends, including after a
|
|
154
|
+
failure or cancellation. Cleanup failures raise because LittleGhost cannot
|
|
155
|
+
claim that the child process and its resources ended cleanly.
|
|
156
|
+
|
|
157
|
+
## Keep a Tool in the conversation
|
|
158
|
+
|
|
159
|
+
With code mode enabled, ordinary Agent Tools move into the program catalog. The
|
|
160
|
+
model-facing controls become `exec`, `wait`, and `stop`. Use `except` when an
|
|
161
|
+
application Tool should remain available to the conversational model instead
|
|
162
|
+
of moving into the program:
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
class ResearchAgent < LittleGhost::Agent
|
|
166
|
+
tools HelpCenterLookupTool, ConfirmTool
|
|
167
|
+
code_mode except: ["confirm_tool"]
|
|
168
|
+
end
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Exclude a Tool when the conversational model should call it as a distinct
|
|
172
|
+
decision—for example, a final confirmation that must remain visible as its own
|
|
173
|
+
step. `except` uses each Tool's model-visible name; `ConfirmTool` defaults to
|
|
174
|
+
`confirm_tool`. Calls made inside and outside code mode share the Agent's
|
|
175
|
+
Tool-call limit. The `exec`, `wait`, and `stop` controls manage execution. They
|
|
176
|
+
do not count toward that application Tool limit themselves.
|
|
177
|
+
|
|
178
|
+
Subagent controls also stay in the conversation. They are orchestration choices
|
|
179
|
+
for the parent model, not functions available inside a code-mode program.
|
|
180
|
+
Code-mode `wait` watches an interpreter program; `wait_for_subagents` checks on
|
|
181
|
+
delegated Agents. [Core Concepts](core_concepts.md#subagents-bring-in-a-specialist)
|
|
182
|
+
explains model-directed delegation.
|
|
183
|
+
|
|
184
|
+
## Set limits for the work you expect
|
|
185
|
+
|
|
186
|
+
The Ruby engine sets limits for source and output size, memory, total and CPU
|
|
187
|
+
time, file size, the number of programs, Tool calls, concurrency, and cleanup.
|
|
188
|
+
Override only the limits your workload needs to change:
|
|
189
|
+
|
|
190
|
+
```ruby
|
|
191
|
+
LittleGhost.configure do |config|
|
|
192
|
+
config.code_mode = {
|
|
193
|
+
engine: :ruby,
|
|
194
|
+
sandbox: :native,
|
|
195
|
+
limits: {
|
|
196
|
+
programs: 16,
|
|
197
|
+
wall_seconds: 900,
|
|
198
|
+
cleanup_seconds: 5
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
end
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Bubblewrap owns the program's process tree but does not cap its process or thread
|
|
205
|
+
count. Use an outer cgroup or container supervisor when generated code needs a
|
|
206
|
+
hard task-count limit.
|
|
207
|
+
|
|
208
|
+
Application defaults apply to every Agent that declares `code_mode`. An Agent
|
|
209
|
+
can override the engine, Sandbox, limits, or excluded Tools in its own
|
|
210
|
+
declaration.
|
|
211
|
+
|
|
212
|
+
The operating-system Sandbox contains the interpreter. The parent Ruby process
|
|
213
|
+
starts it, brokers Tool calls, and cleans it up. Language restrictions alone
|
|
214
|
+
cannot contain native extensions, interpreter bugs, files, subprocesses, or
|
|
215
|
+
sockets.
|
|
216
|
+
|
|
217
|
+
Use an enforcing Sandbox backend for model-written code. Before production,
|
|
218
|
+
test the deployed backend against the files, child processes, networking, and
|
|
219
|
+
resource pressure your application expects. Also test cancellation and cleanup
|
|
220
|
+
on the deployed host.
|
|
221
|
+
|
|
222
|
+
## Opt into JavaScript when it fits
|
|
223
|
+
|
|
224
|
+
The JavaScript engine is optional. It uses MiniRacer and gives each program its
|
|
225
|
+
own V8 global state. The core gem does not require or load MiniRacer:
|
|
226
|
+
|
|
227
|
+
```ruby
|
|
228
|
+
# Gemfile
|
|
229
|
+
gem "mini_racer", "~> 0.21"
|
|
230
|
+
|
|
231
|
+
# application setup
|
|
232
|
+
require "little_ghost/code_mode/javascript_engine"
|
|
233
|
+
|
|
234
|
+
LittleGhost.configure do |config|
|
|
235
|
+
config.code_mode = {engine: :javascript, sandbox: :native}
|
|
236
|
+
end
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
The JavaScript program has no Node.js APIs, filesystem, network, console,
|
|
240
|
+
WebAssembly, or process-spawning API. Tool methods return Promises, and the
|
|
241
|
+
generated instructions include TypeScript declarations derived from each Tool
|
|
242
|
+
schema. Use `await` or `Promise.all`, `text(value)` for output, and `exit()` to
|
|
243
|
+
complete early. Call `text(value)` first when the value should become
|
|
244
|
+
user-visible output.
|
|
245
|
+
|
|
246
|
+
MiniRacer's language-level restrictions are useful, but the operating-system
|
|
247
|
+
Sandbox still contains the program. The Ruby parent owns the Tool catalog,
|
|
248
|
+
permission checks, Tool-call limits, events, tracing, and resource cleanup.
|
|
249
|
+
|
|
250
|
+
## Build a custom engine
|
|
251
|
+
|
|
252
|
+
Applications can register another `CodeMode::Engine`. An engine names its
|
|
253
|
+
language, writes the instructions shown to the model, and opens a
|
|
254
|
+
`CodeMode::Session`. The session implements `#execute`, `#wait`, `#stop`, and
|
|
255
|
+
`#close`. The first three operations return a `CodeMode::ProgramResult`.
|
|
256
|
+
|
|
257
|
+
LittleGhost gives the engine a Tool broker and a Sandbox factory. The broker
|
|
258
|
+
stays in the parent Ruby process. The factory creates the Sandbox that
|
|
259
|
+
contains model-written code. An engine may request named runtime paths for its
|
|
260
|
+
interpreter libraries. Those paths are visible to the child process, but they
|
|
261
|
+
never become filesystem grants available through Tools.
|
|
262
|
+
|
|
263
|
+
The session owns every Workspace, Sandbox, child process, thread, and
|
|
264
|
+
communication channel it creates. It closes those resources in reverse order.
|
|
265
|
+
LittleGhost may use one registered engine instance for concurrent Agent Runs,
|
|
266
|
+
so the engine must keep each program's mutable state inside its session.
|
|
267
|
+
|
|
268
|
+
A sandboxed engine must use a backend that owns the complete child process tree
|
|
269
|
+
or can prevent child processes. An explicitly unrestricted backend may run an
|
|
270
|
+
engine, but the generated program then has the same host access as the parent.
|
|
271
|
+
|
|
272
|
+
See `LittleGhost::CodeMode::Engine`, `LittleGhost::CodeMode::Session`, and
|
|
273
|
+
`LittleGhost::CodeMode::ProgramResult` for the extension contract. Continue
|
|
274
|
+
with [Integrations](integrations.md) to connect Runs to MCP tools, AG-UI, and
|
|
275
|
+
OpenTelemetry.
|