little_ghost 0.4.0 → 0.6.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 +276 -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 +190 -17
- 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 +355 -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 +250 -48
- 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 +64 -20
- data/lib/little_ghost/assembly_builder.rb +26 -26
- data/lib/little_ghost/assembly_execution.rb +23 -11
- data/lib/little_ghost/code_mode/broker.rb +168 -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 +578 -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 +208 -21
- 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 +32 -27
- data/lib/little_ghost/execution_state.rb +3 -3
- data/lib/little_ghost/graph.rb +373 -210
- data/lib/little_ghost/instrumentation.rb +2 -1
- 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 +104 -0
- data/lib/little_ghost/network/envoy_config.rb +362 -0
- data/lib/little_ghost/network/envoy_gateway.rb +418 -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 +89 -25
- data/lib/little_ghost/run_context.rb +11 -1
- 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 +111 -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 +240 -0
- data/lib/little_ghost/session.rb +30 -6
- data/lib/little_ghost/session_stores/filesystem.rb +29 -13
- 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 +182 -79
- data/lib/little_ghost/support/callbacks.rb +3 -1
- data/lib/little_ghost/support/cancellation_token.rb +2 -2
- data/lib/little_ghost/support/content_capture.rb +3 -3
- data/lib/little_ghost/support/executor.rb +84 -15
- data/lib/little_ghost/support/http_client.rb +11 -4
- data/lib/little_ghost/support/interruptible_stream.rb +4 -2
- data/lib/little_ghost/support/pooled_thread_runner.rb +126 -0
- data/lib/little_ghost/support/redactor.rb +1 -1
- data/lib/little_ghost/support/serialized_dispatcher.rb +84 -0
- data/lib/little_ghost/support/task.rb +126 -0
- data/lib/little_ghost/support/task_runner.rb +74 -0
- data/lib/little_ghost/support.rb +4 -0
- 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/workflow.rb +3 -3
- data/lib/little_ghost/workspace.rb +222 -8
- data/lib/little_ghost.rb +38 -12
- metadata +116 -2
- data/lib/little_ghost/unrestricted_sandbox.rb +0 -306
data/docs/guides/production.md
CHANGED
|
@@ -39,7 +39,9 @@ end
|
|
|
39
39
|
|
|
40
40
|
Provider connections and model roles can also live in YAML files under `config/little_ghost`, or in files you select explicitly. Values set in Ruby take priority. An explicitly selected file comes next, followed by conventional files and environment defaults. See `LittleGhost::Configuration` when you need every supported source and override.
|
|
41
41
|
|
|
42
|
-
Prompts, caller input and history,
|
|
42
|
+
Prompts, caller input and history, Tool results, and attachments may go to the
|
|
43
|
+
selected provider. Choose configured providers that are appropriate for that
|
|
44
|
+
data and its retention or residency needs.
|
|
43
45
|
|
|
44
46
|
## Configure once, call from anywhere
|
|
45
47
|
|
|
@@ -73,6 +75,103 @@ The controller supplies identity and account access from authenticated applicati
|
|
|
73
75
|
|
|
74
76
|
Configure LittleGhost before the first Agent or Assembly call. Once application services start successfully, the configuration is locked so every request sees one stable setup.
|
|
75
77
|
|
|
78
|
+
## Use an existing fiber scheduler
|
|
79
|
+
|
|
80
|
+
If your application already runs inside a Ruby Fiber scheduler, LittleGhost
|
|
81
|
+
uses it for parallel Tool calls, Workflow and Graph branches, subagent turns,
|
|
82
|
+
background Executions, and nested code-mode Tool calls. LittleGhost does not
|
|
83
|
+
install or run a scheduler. Your scheduler must support `Fiber.schedule`. The
|
|
84
|
+
optional `async` gem used below does.
|
|
85
|
+
|
|
86
|
+
For example, an application using the optional `async` gem can let several
|
|
87
|
+
requests make progress on one thread while each request waits for network I/O.
|
|
88
|
+
Add `gem "async"` to the application's bundle, then start the calls inside an
|
|
89
|
+
Async task:
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
require "async"
|
|
93
|
+
|
|
94
|
+
questions = [
|
|
95
|
+
"How long do refunds take?",
|
|
96
|
+
"Can I update my delivery address?"
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
answers = Async do |task|
|
|
100
|
+
questions.map do |question|
|
|
101
|
+
task.async { CustomerSupportAgent.ask(question).response }
|
|
102
|
+
end.map(&:wait)
|
|
103
|
+
end.wait
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The default `:auto` setting uses fibers when a call begins inside a scheduled
|
|
107
|
+
fiber and worker threads everywhere else. Set `concurrency_backend` to `:fiber`
|
|
108
|
+
when calling outside a scheduled fiber should be an application error.
|
|
109
|
+
LittleGhost then raises `LittleGhost::ConfigurationError` instead of quietly
|
|
110
|
+
starting a thread.
|
|
111
|
+
|
|
112
|
+
### Keep one blocking call from pausing other fibers
|
|
113
|
+
|
|
114
|
+
Fiber scheduling helps while work waits for I/O; it does not make CPU-heavy
|
|
115
|
+
Ruby code run in parallel. Whether an I/O call lets other fibers run depends on
|
|
116
|
+
the Ruby version, scheduler, and library. Call libraries normally at first. If
|
|
117
|
+
documentation or measurement shows that one call pauses the other fibers—and
|
|
118
|
+
the work can continue on another Ruby thread—wrap that call:
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
article = LittleGhost.offload_blocking do
|
|
122
|
+
HelpCenterSearch.lookup(question)
|
|
123
|
+
end
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Outside a scheduled fiber, `offload_blocking` runs the block inline. Inside one,
|
|
127
|
+
it uses LittleGhost's shared pool of reusable threads so the other fibers can
|
|
128
|
+
continue. If the caller is cancelled after the pool accepts the block,
|
|
129
|
+
LittleGhost waits for the block to finish before reporting the cancellation.
|
|
130
|
+
The helper does not add a timeout or cancellation mechanism to the underlying
|
|
131
|
+
call, so use the library's controls when available.
|
|
132
|
+
|
|
133
|
+
If most of your Tool or extension code prevents other fibers from running, use
|
|
134
|
+
worker threads for all LittleGhost concurrency instead:
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
LittleGhost.configure do |config|
|
|
138
|
+
config.concurrency_backend = :thread
|
|
139
|
+
end
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
LittleGhost also keeps a few jobs on threads so it can finish or clean them up
|
|
143
|
+
reliably. Provider and subprocess streams may use dedicated threads. Inside a
|
|
144
|
+
scheduled fiber, certificate generation and Filesystem SessionStore
|
|
145
|
+
transactions use the same shared pool as `offload_blocking`. A Run may
|
|
146
|
+
therefore still create or use threads when its concurrency backend is `:fiber`.
|
|
147
|
+
|
|
148
|
+
The blocking pool allows two operations at a time by default. Applications
|
|
149
|
+
that observe calls waiting for a pool worker can increase its process-wide
|
|
150
|
+
capacity during startup, before any call can start the pool:
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
LittleGhost.configure do |config|
|
|
154
|
+
config.blocking_pool_capacity 4
|
|
155
|
+
end
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Increasing this value permits more operating-system threads. It does not
|
|
159
|
+
change Tool, Workflow, Graph, or subagent concurrency. The value is
|
|
160
|
+
process-wide; every `Configuration` reads and writes the same setting.
|
|
161
|
+
|
|
162
|
+
### Protect state shared by concurrent calls
|
|
163
|
+
|
|
164
|
+
LittleGhost may call a shared Tool, provider, SessionStore, hook, or callback
|
|
165
|
+
from different threads. Fibers can also take turns entering the same object on
|
|
166
|
+
one thread. Protect shared mutable state, keep lock scope narrow, and do not
|
|
167
|
+
call application callbacks while holding a lock.
|
|
168
|
+
|
|
169
|
+
Pass request-specific values through the Invocation context or another explicit
|
|
170
|
+
argument. Do not use `thread_variable_set` for request state because every
|
|
171
|
+
fiber on the thread shares those values. `Thread.current[:key]` is fiber-local,
|
|
172
|
+
but LittleGhost does not copy application-defined entries into each worker
|
|
173
|
+
task.
|
|
174
|
+
|
|
76
175
|
## Preserve conversation with Sessions
|
|
77
176
|
|
|
78
177
|
A **Session** lets one request continue an earlier conversation. Pass the same session ID and trusted actor ID with each related call:
|
|
@@ -104,7 +203,8 @@ Every Run has a session ID so LittleGhost can checkpoint its progress. If you do
|
|
|
104
203
|
|
|
105
204
|
## Stream or supervise long-running work
|
|
106
205
|
|
|
107
|
-
`.stream_ask` runs on the caller's thread and yields `StreamEvent`
|
|
206
|
+
`.stream_ask` runs on the caller's fiber or thread and yields `StreamEvent`
|
|
207
|
+
values as the answer arrives:
|
|
108
208
|
|
|
109
209
|
```ruby
|
|
110
210
|
stream = CustomerSupportAgent.stream_ask(question)
|
|
@@ -116,6 +216,14 @@ end
|
|
|
116
216
|
record_outcome(run.outcome, error_type: run.error&.class&.name)
|
|
117
217
|
```
|
|
118
218
|
|
|
219
|
+
Composite assembly streams include intermediate and nested Agent work as `:agent_stream` events. This default also applies to the event consumer passed to `start_execution`. Pass `include_agent_events: false` when only the ordinary public stream is needed.
|
|
220
|
+
|
|
221
|
+
> **Safety note:** Contextual events can include inputs, reasoning, Tool
|
|
222
|
+
> arguments and results, errors, and output from every participant. Check that
|
|
223
|
+
> the destination may see the complete Run, or filter the events before sending
|
|
224
|
+
> or storing them. LittleGhost's AG-UI adapter ignores these events unless the
|
|
225
|
+
> application translates them explicitly.
|
|
226
|
+
|
|
119
227
|
Use `start_execution` when the caller must stay free for other work, or when you want to deliver an interjection to an active response:
|
|
120
228
|
|
|
121
229
|
```ruby
|
|
@@ -128,13 +236,24 @@ execution.wait(deadline: Time.now + 30)
|
|
|
128
236
|
execution.run.completed?
|
|
129
237
|
```
|
|
130
238
|
|
|
131
|
-
The event block runs on the
|
|
239
|
+
The event block runs on the same fiber or thread as the Execution. With `:auto`,
|
|
240
|
+
`start_execution` uses a fiber when its caller is already in a scheduled fiber;
|
|
241
|
+
otherwise, it uses a worker thread. Keep the block quick because it slows event
|
|
242
|
+
delivery while it runs. Cancellation, deadlines, and `close` ask the work to
|
|
243
|
+
stop; they cannot forcibly end arbitrary provider or Tool code or undo actions
|
|
244
|
+
that already happened. Keep the application's scheduler running until an
|
|
245
|
+
Execution using it finishes or closes.
|
|
132
246
|
|
|
133
|
-
##
|
|
247
|
+
## Keep Tool permission checks in application code
|
|
134
248
|
|
|
135
|
-
A
|
|
249
|
+
A Tool schema checks the shape of model-supplied input. Your application still
|
|
250
|
+
owns permission checks, safe retries, rate limits, and auditing. An ordinary
|
|
251
|
+
Tool runs in the Ruby process; a Sandbox contains only file or child-process
|
|
252
|
+
work that deliberately passes through it.
|
|
136
253
|
|
|
137
|
-
Use the Tool binding's `run` to read current, application-established values
|
|
254
|
+
Use the Tool binding's `run` to read current, application-established values
|
|
255
|
+
from `run.invocation.context`. Don't rely on model arguments for identity or
|
|
256
|
+
account membership.
|
|
138
257
|
|
|
139
258
|
Treat `RunContext#state` as mutable working and Session state. Revalidate anything restored from an earlier request. Synchronize access when parallel Tools share mutable state, or mark every Tool that reads or changes it as `exclusive true`.
|
|
140
259
|
|
|
@@ -142,27 +261,72 @@ A `ToolError` message is visible to the model, so keep it safe to share. LittleG
|
|
|
142
261
|
|
|
143
262
|
When a step retries, its tool calls may happen again too. Prefer read-only work, idempotency keys, or operations that are safe to repeat.
|
|
144
263
|
|
|
264
|
+
[Tools](tools.md) develops this pattern from the first application Tool through
|
|
265
|
+
bindings, concurrency, sandbox delegation, and code mode.
|
|
266
|
+
|
|
145
267
|
## Choose workspace and sandbox behavior explicitly
|
|
146
268
|
|
|
147
|
-
A Workspace
|
|
269
|
+
A **Workspace** names the host paths associated with a Run. A **Sandbox**
|
|
270
|
+
controls filesystem operations and child processes deliberately sent through
|
|
271
|
+
it. A custom Ruby Tool stays in the application process unless it delegates
|
|
272
|
+
work to the bound Sandbox.
|
|
273
|
+
|
|
274
|
+
The dependency-free default is an application-root Workspace with `LittleGhost::Sandboxes::Unrestricted`. It is not process or network isolation. Select an enforcing backend explicitly when a model can influence commands; LittleGhost raises when that backend is unavailable instead of silently falling back:
|
|
275
|
+
|
|
276
|
+
```ruby
|
|
277
|
+
require "fileutils"
|
|
278
|
+
require "tmpdir"
|
|
279
|
+
|
|
280
|
+
LittleGhost.configure do |config|
|
|
281
|
+
config.workspace = lambda do |**|
|
|
282
|
+
root = Dir.mktmpdir("little-ghost-support-")
|
|
283
|
+
LittleGhost::Workspace.new(
|
|
284
|
+
root:,
|
|
285
|
+
teardown: lambda do |workspace:, **|
|
|
286
|
+
FileUtils.remove_entry_secure(workspace.root) if File.exist?(workspace.root)
|
|
287
|
+
end
|
|
288
|
+
)
|
|
289
|
+
end
|
|
290
|
+
config.sandbox = {
|
|
291
|
+
provider: :native,
|
|
292
|
+
files: {root: :read_write},
|
|
293
|
+
root_filesystem: :isolated,
|
|
294
|
+
environment: {inherit: false, set: {"LANG" => "C.UTF-8"}},
|
|
295
|
+
network: :none
|
|
296
|
+
}
|
|
297
|
+
end
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
The temporary Workspace above is useful when files should live for one Run. Use application-managed storage, with tenant isolation and concurrency control, when files must persist or several Runs share a root.
|
|
301
|
+
|
|
302
|
+
The Run opens a Runtime-created Workspace before its Sandbox and closes them in reverse order after every outcome. Closing a Workspace invokes its configured teardown; it does not delete files by default. Cleanup failures raise because LittleGhost cannot confirm a clean shutdown of every owned resource. Existing instances passed by the application remain caller-owned.
|
|
148
303
|
|
|
149
|
-
|
|
304
|
+
[Workspaces and Sandboxes](sandboxing.md) explains backend selection, logical
|
|
305
|
+
Workspace paths, files, process-only runtime paths, Scopes, filtered
|
|
306
|
+
networking, and deployment validation in depth.
|
|
150
307
|
|
|
151
|
-
|
|
308
|
+
[Code Mode](code_mode.md) applies that setup to model-authored Ruby or
|
|
309
|
+
optional JavaScript that composes the Agent's existing Tools.
|
|
152
310
|
|
|
153
|
-
##
|
|
311
|
+
## Protect data in telemetry
|
|
154
312
|
|
|
155
313
|
LittleGhost emits events as a request starts, calls a model or tool, moves between assembly steps, retries, and finishes. Instrumentation subscribers and OpenTelemetry exporters can send those events to your monitoring system.
|
|
156
314
|
|
|
157
|
-
An external telemetry service may receive application identifiers and event
|
|
315
|
+
An external telemetry service may receive application identifiers and event
|
|
316
|
+
data. Redact sensitive values before exporting them. Avoid attributes with many
|
|
317
|
+
unique values, such as raw order or request IDs.
|
|
158
318
|
|
|
159
|
-
A composite `RunResult` includes short step summaries and trajectory queries.
|
|
319
|
+
A composite `RunResult` includes short step summaries and trajectory queries.
|
|
320
|
+
Keep detailed provider errors and sensitive diagnostics in your monitoring
|
|
321
|
+
system, not in model or user responses.
|
|
160
322
|
|
|
161
|
-
##
|
|
323
|
+
## Know what the Run closes and raises
|
|
162
324
|
|
|
163
325
|
One top-level Run owns the workspace and sandbox that LittleGhost creates for it, plus application resources registered with `run.register`. It closes those resources after success, failure, a partial response, or cancellation. Existing workspace or sandbox instances passed by the application remain caller-owned.
|
|
164
326
|
|
|
165
|
-
Ordinary execution failures appear on the Run and its final event. Cleanup,
|
|
327
|
+
Ordinary execution failures appear on the Run and its final event. Cleanup,
|
|
328
|
+
event delivery, or instrumentation can still raise an exception when
|
|
329
|
+
LittleGhost cannot promise a clean ending.
|
|
166
330
|
|
|
167
331
|
## Advanced: work with Runtime directly
|
|
168
332
|
|
|
@@ -176,11 +340,20 @@ runtime = LittleGhost::Runtime.new(configuration: configuration)
|
|
|
176
340
|
agent = CustomerSupportAgent.new(runtime: runtime)
|
|
177
341
|
```
|
|
178
342
|
|
|
179
|
-
An explicit Runtime
|
|
343
|
+
An explicit Runtime has its own independent configuration. It does not replace
|
|
344
|
+
LittleGhost's shared default.
|
|
180
345
|
|
|
181
|
-
One Runtime can serve independent calls from several threads
|
|
346
|
+
One Runtime can serve independent calls from several threads and fibers. Each
|
|
347
|
+
call gets its own Run, participants, Tools, and Runtime-created Workspace and
|
|
348
|
+
Sandbox. An Agent or Assembly already bound to an active Run must stay with
|
|
349
|
+
that Run.
|
|
182
350
|
|
|
183
|
-
Within one SessionStore instance, LittleGhost serializes calls sharing a
|
|
351
|
+
Within one SessionStore instance, LittleGhost serializes calls sharing a
|
|
352
|
+
Session. Multi-process deployments need coordination from their store. Custom
|
|
353
|
+
stores and other shared extension objects may receive concurrent calls. Calls
|
|
354
|
+
can overlap on different threads, or fibers can take turns entering the same
|
|
355
|
+
object on one thread. Protect shared mutable state without relying on thread
|
|
356
|
+
identity.
|
|
184
357
|
|
|
185
358
|
Runtime has no shutdown step. Shared services supplied by the application keep their own lifecycle. Shut those services down with the rest of your application. If you installed process-wide instrumentation subscribers, flush or shut down `LittleGhost::Instrumentation` during application shutdown.
|
|
186
359
|
|
data/docs/guides/prompt_views.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A short prompt fits nicely inside an Agent class. As the instructions grow, move them into a **prompt view**: an ERB file that LittleGhost finds and renders for the Agent.
|
|
4
4
|
|
|
5
|
-
This keeps the Agent
|
|
5
|
+
This keeps the Agent definition focused. It also gives shared instructions and application values a natural home.
|
|
6
6
|
|
|
7
7
|
## Start with the inline prompt
|
|
8
8
|
|
|
@@ -90,7 +90,7 @@ You are a customer support agent for <%= company_name %>.
|
|
|
90
90
|
|
|
91
91
|
The underscore marks a partial. Its locals are explicit, so it does not quietly inherit everything available to the parent view.
|
|
92
92
|
|
|
93
|
-
##
|
|
93
|
+
## Choose a different template path
|
|
94
94
|
|
|
95
95
|
Most named Agents can rely on their conventional path. Use `system_template` when a class should read a differently named view:
|
|
96
96
|
|
|
@@ -106,15 +106,20 @@ LittleGhost chooses one prompt source in this order:
|
|
|
106
106
|
2. An explicit `system_template`
|
|
107
107
|
3. The Agent's conventional `system.erb` view
|
|
108
108
|
|
|
109
|
-
Applications can add prompt lookup roots through `Configuration#prompt_paths`.
|
|
109
|
+
Applications can add prompt lookup roots through `Configuration#prompt_paths`.
|
|
110
|
+
Earlier roots win, which lets application code override a shared prompt package.
|
|
110
111
|
|
|
111
112
|
## Treat views as application code
|
|
112
113
|
|
|
113
|
-
Prompt views run as ERB inside the Ruby process
|
|
114
|
+
Prompt views run as ERB inside the Ruby process and can call Ruby. Keep prompt
|
|
115
|
+
directories with the rest of your application code rather than letting a
|
|
116
|
+
request choose one.
|
|
114
117
|
|
|
115
|
-
`TrustedPath`
|
|
118
|
+
`TrustedPath` is available for the uncommon case where application code selects
|
|
119
|
+
a request-specific root. It marks that choice explicitly; it does not inspect
|
|
120
|
+
or restrict the directory.
|
|
116
121
|
|
|
117
|
-
##
|
|
122
|
+
## Build request-specific input in a Workflow
|
|
118
123
|
|
|
119
124
|
A prompt view defines reusable instructions for one Agent. A Workflow may still build request-specific input for that Agent:
|
|
120
125
|
|
|
@@ -129,4 +134,6 @@ MESSAGE
|
|
|
129
134
|
|
|
130
135
|
The Workflow is composing this request. `CustomerSupportAgent` still receives its own system prompt view when it runs.
|
|
131
136
|
|
|
132
|
-
Continue with [
|
|
137
|
+
Continue with [Tools](tools.md) to give those Agents application capabilities
|
|
138
|
+
while keeping model input, trusted context, and delegated sandbox operations
|
|
139
|
+
distinct.
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# Workspaces and Sandboxes
|
|
2
|
+
|
|
3
|
+
A Workspace gives one Run—one top-level Agent or Assembly execution—a stable
|
|
4
|
+
set of host paths. A Sandbox decides which operations may reach those paths and
|
|
5
|
+
how child processes are contained.
|
|
6
|
+
|
|
7
|
+
Use them together when an Agent can read files, change files, or run programs:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
require "fileutils"
|
|
11
|
+
require "tmpdir"
|
|
12
|
+
|
|
13
|
+
LittleGhost.configure do |config|
|
|
14
|
+
config.workspace = lambda do |**|
|
|
15
|
+
root = Dir.mktmpdir("little-ghost-support-")
|
|
16
|
+
LittleGhost::Workspace.new(
|
|
17
|
+
root:,
|
|
18
|
+
teardown: lambda do |workspace:, **|
|
|
19
|
+
FileUtils.remove_entry_secure(workspace.root) if File.exist?(workspace.root)
|
|
20
|
+
end
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
config.sandbox = {
|
|
25
|
+
provider: :native,
|
|
26
|
+
files: {root: :read_write},
|
|
27
|
+
root_filesystem: :isolated,
|
|
28
|
+
environment: {inherit: false, set: {"LANG" => "C.UTF-8"}},
|
|
29
|
+
network: :none
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This example gives each Run a temporary writable root and removes it during
|
|
35
|
+
teardown. The `:native` Sandbox backend selects Seatbelt on macOS or Bubblewrap on
|
|
36
|
+
Linux. It raises instead of running without isolation when the native backend
|
|
37
|
+
is unavailable.
|
|
38
|
+
|
|
39
|
+
`LittleGhost::Tools::Filesystem` and `LittleGhost::Tools::Shell` use the Sandbox
|
|
40
|
+
assigned to the current Run. Code-mode interpreters also run inside their own
|
|
41
|
+
Sandbox. Ordinary application Tools, callbacks, and provider requests stay in
|
|
42
|
+
the Ruby process unless they deliberately delegate an operation.
|
|
43
|
+
|
|
44
|
+
The split looks like this:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
Ruby application process
|
|
48
|
+
├── provider requests
|
|
49
|
+
├── application Tool#call
|
|
50
|
+
└── Sandbox
|
|
51
|
+
├── bounded filesystem operations
|
|
52
|
+
├── Shell child processes
|
|
53
|
+
└── code-mode interpreter processes
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Give files a stable home
|
|
57
|
+
|
|
58
|
+
A Workspace names the files available during a Run and controls their setup and
|
|
59
|
+
cleanup. Its `root` is the default working directory. Named paths give important
|
|
60
|
+
directories logical identities:
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
workspace = LittleGhost::Workspace.new(
|
|
64
|
+
root: "/var/lib/support/run-481",
|
|
65
|
+
paths: {
|
|
66
|
+
source: "/srv/support/source",
|
|
67
|
+
skills: "skills",
|
|
68
|
+
home: "runtime-home"
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Relative named paths live beneath the root and are created when the Workspace
|
|
74
|
+
opens. Absolute named paths are references deliberately supplied by the
|
|
75
|
+
application and must already exist. A setup callback can provision them when
|
|
76
|
+
needed.
|
|
77
|
+
|
|
78
|
+
Opening resolves real paths and records which physical directories they refer
|
|
79
|
+
to. LittleGhost rejects two names that point to the same directory and later
|
|
80
|
+
fails closed if a configured directory is replaced. Nested paths are allowed,
|
|
81
|
+
and the most restrictive matching access wins.
|
|
82
|
+
|
|
83
|
+
Workspace object lifetime and file lifetime are separate. Closing invokes the
|
|
84
|
+
configured teardown callback, but does not delete files by default. Use a
|
|
85
|
+
run-scoped temporary root for disposable work. Use application-managed storage,
|
|
86
|
+
tenant isolation, and concurrency control when several Runs share files.
|
|
87
|
+
|
|
88
|
+
## Give artifacts logical references
|
|
89
|
+
|
|
90
|
+
Images and documents normally remain provider content. When filesystem Tools
|
|
91
|
+
or code mode should read the same bytes, configure the conventional artifact
|
|
92
|
+
path and enable artifact handling:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
LittleGhost.configure do |config|
|
|
96
|
+
config.workspace = {
|
|
97
|
+
provider: :directory,
|
|
98
|
+
root: "tmp/agent-runs",
|
|
99
|
+
paths: {artifacts: "artifacts"}
|
|
100
|
+
}
|
|
101
|
+
config.artifacts
|
|
102
|
+
end
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
LittleGhost stores input images and documents after the Workspace and Sandbox
|
|
106
|
+
open. The model receives the image or document in its normal input without a
|
|
107
|
+
second text reference. Filesystem Tools can list `workspace://artifacts` when a
|
|
108
|
+
later operation needs the stored copy. Messages added while the Run is active
|
|
109
|
+
receive the same treatment.
|
|
110
|
+
|
|
111
|
+
LittleGhost limits the number and size of files stored from each message and
|
|
112
|
+
across the complete Run. Those limits also include Tool artifacts and oversized
|
|
113
|
+
Tool results. A successful operation stores all of its files. If storage fails
|
|
114
|
+
partway through, LittleGhost attempts to remove that operation's files and
|
|
115
|
+
reports a cleanup error when it cannot. Stored files use private permissions,
|
|
116
|
+
and the Workspace provider still owns final cleanup.
|
|
117
|
+
|
|
118
|
+
Declaring the path does not grant model access. When the Agent should read it,
|
|
119
|
+
grant the Sandbox access to `:artifacts` and include a filesystem Tool.
|
|
120
|
+
|
|
121
|
+
## Pass logical paths, not host layout
|
|
122
|
+
|
|
123
|
+
The Filesystem Tool accepts root-relative paths such as `notes/today.md`.
|
|
124
|
+
Named paths use references such as
|
|
125
|
+
`workspace://skills/refunds/SKILL.md`.
|
|
126
|
+
|
|
127
|
+
Those references remain meaningful without exposing or translating the host
|
|
128
|
+
layout. The Filesystem Tool rejects physical absolute paths. Child processes
|
|
129
|
+
use the Workspace's real paths directly, start in its root, and receive
|
|
130
|
+
`LITTLE_GHOST_WORKSPACE_ROOT` plus one
|
|
131
|
+
`LITTLE_GHOST_WORKSPACE_<NAME>` variable for each named path.
|
|
132
|
+
|
|
133
|
+
Workspace references give application code one stable path format. Each
|
|
134
|
+
Sandbox backend applies the configured access using the filesystem controls
|
|
135
|
+
available on its host.
|
|
136
|
+
|
|
137
|
+
## Separate Tool-visible files from process support
|
|
138
|
+
|
|
139
|
+
Sandbox configuration divides named paths into two groups:
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
config.sandbox = {
|
|
143
|
+
provider: :native,
|
|
144
|
+
files: {
|
|
145
|
+
root: :read_write,
|
|
146
|
+
source: :read_only,
|
|
147
|
+
skills: :read_only
|
|
148
|
+
},
|
|
149
|
+
runtime_paths: {
|
|
150
|
+
home: :read_write
|
|
151
|
+
},
|
|
152
|
+
network: :none
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`files` are visible to both the Filesystem Tool and sandboxed processes.
|
|
157
|
+
`runtime_paths` are process-only. Use runtime paths for interpreter libraries,
|
|
158
|
+
homes, sockets, and service state that a model should not browse through a
|
|
159
|
+
filesystem Tool.
|
|
160
|
+
|
|
161
|
+
This is visibility, not secrecy from the process. A child with a runtime-path
|
|
162
|
+
grant can use that path according to its access mode. The distinction prevents
|
|
163
|
+
the Filesystem Tool from offering it as a model-visible file
|
|
164
|
+
tree.
|
|
165
|
+
|
|
166
|
+
## Narrow access with a Scope
|
|
167
|
+
|
|
168
|
+
A `Sandbox::Scope` is a non-owning, reduced view of a Sandbox. It can remove
|
|
169
|
+
paths, change writable access to read-only, remove capabilities (categories of
|
|
170
|
+
allowed operations), or narrow networking. It cannot widen its parent:
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
reviewer = run.sandbox.scope(
|
|
174
|
+
files: {source: :read_only},
|
|
175
|
+
runtime_paths: [],
|
|
176
|
+
capabilities: %i[filesystem_read filesystem_list],
|
|
177
|
+
network: false
|
|
178
|
+
)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Scopes constrain only code that receives and uses the Scope. Code that keeps a
|
|
182
|
+
reference to the parent Sandbox keeps the parent's authority. A Scope does not
|
|
183
|
+
open or close its parent and owns no resources.
|
|
184
|
+
|
|
185
|
+
## Choose how much of the host exists
|
|
186
|
+
|
|
187
|
+
`root_filesystem` controls what a sandboxed process can see beyond declared
|
|
188
|
+
Workspace paths:
|
|
189
|
+
|
|
190
|
+
- `:isolated` exposes only declared paths and required runtime support. It is
|
|
191
|
+
the default and the right starting point for generated interpreters.
|
|
192
|
+
- `:read_only` exposes the host filesystem for development commands while
|
|
193
|
+
confining writes to declared writable paths. It makes installed compilers,
|
|
194
|
+
package managers, profiles, and toolchains available, but the child can also
|
|
195
|
+
read host files unless the Ruby process already runs inside a container or VM
|
|
196
|
+
that prevents those reads.
|
|
197
|
+
- `:read_write` grants the host filesystem directly. Treat it as unrestricted
|
|
198
|
+
host authority.
|
|
199
|
+
|
|
200
|
+
On Seatbelt, host-visible modes permit subprocesses because development tools
|
|
201
|
+
often need them. A Scope can remove `process_spawn` for a command that does not.
|
|
202
|
+
|
|
203
|
+
## Choose an enforcement backend
|
|
204
|
+
|
|
205
|
+
| Sandbox backend | Host | What it enforces |
|
|
206
|
+
| --- | --- | --- |
|
|
207
|
+
| `:native` | macOS or Linux | Selects Seatbelt on macOS and Bubblewrap on Linux; fails closed elsewhere |
|
|
208
|
+
| `:seatbelt` | macOS | Deny-default Seatbelt profile over the configured physical paths |
|
|
209
|
+
| `:bubblewrap` | Linux | Fresh user, PID, mount, IPC, UTS, and optional network namespaces |
|
|
210
|
+
| `:unrestricted` | Ruby platforms | No containment; commands have the application process's host authority |
|
|
211
|
+
|
|
212
|
+
`LittleGhost::Sandbox.probe(:native)` reports whether the platform backend is
|
|
213
|
+
available. Selecting an enforcing backend never falls back to unrestricted
|
|
214
|
+
execution.
|
|
215
|
+
|
|
216
|
+
Bubblewrap owns descendants with a PID namespace and ends them when the
|
|
217
|
+
supervising process dies. It cannot selectively deny fork inside that
|
|
218
|
+
namespace, so a request for
|
|
219
|
+
`allow_subprocesses: false` fails closed rather than claiming an unenforced
|
|
220
|
+
restriction. Bubblewrap does not impose a task-count limit by itself. Use an
|
|
221
|
+
outer cgroup or container supervisor when generated code needs a hard cap on
|
|
222
|
+
the processes and threads it can create.
|
|
223
|
+
|
|
224
|
+
Seatbelt can constrain spawned children, but macOS has no PID namespace. It
|
|
225
|
+
terminates the command process group during cleanup; a child that deliberately
|
|
226
|
+
detaches from that group may survive. Use an outer process or container
|
|
227
|
+
supervisor when complete descendant ownership is required on macOS.
|
|
228
|
+
|
|
229
|
+
> **Safety note:** `LittleGhost::Sandboxes::Unrestricted` is suitable for
|
|
230
|
+
> application commands and tests that you would already run directly. It
|
|
231
|
+
> validates paths and bounds output, but it does not isolate the command from
|
|
232
|
+
> the host. Use `:native` for generated commands in production.
|
|
233
|
+
|
|
234
|
+
## Keep process ownership explicit
|
|
235
|
+
|
|
236
|
+
`Sandbox#start_program` returns a `Sandbox::ProcessSession` with bounded input
|
|
237
|
+
and output, `alive?`, bounded `wait(timeout:)`, `terminate`, and `close`. The
|
|
238
|
+
session starts the command in a process group so cleanup can stop it and its
|
|
239
|
+
ordinary descendants together. It applies available CPU, memory, file, and
|
|
240
|
+
output limits, requests termination, and then forces termination when needed.
|
|
241
|
+
|
|
242
|
+
Callers that open a ProcessSession own it and must close it. LittleGhost fails
|
|
243
|
+
closed when it cannot supervise a configured memory limit. The parent samples
|
|
244
|
+
the visible process tree every 100 milliseconds, so it reacts only to memory
|
|
245
|
+
present at a sample and may miss peaks between samples. On Linux, three
|
|
246
|
+
consecutive failures to read the root process or the `/proc` snapshot end the
|
|
247
|
+
process. Use an outer cgroup or container when memory must be enforced as a hard
|
|
248
|
+
limit by the operating system.
|
|
249
|
+
|
|
250
|
+
A Run closes the Workspace and Sandbox it creates after success, failure, a
|
|
251
|
+
partial response, or cancellation. Application-supplied instances remain
|
|
252
|
+
caller-owned.
|
|
253
|
+
|
|
254
|
+
## Configure child-process networking separately
|
|
255
|
+
|
|
256
|
+
Sandbox networking has three modes:
|
|
257
|
+
|
|
258
|
+
- `:none` removes child networking.
|
|
259
|
+
- `:inherit` permits the selected Sandbox backend's ordinary network access.
|
|
260
|
+
- `:allowlist` requires an enforcing gateway: a supervised proxy that permits
|
|
261
|
+
only configured destinations.
|
|
262
|
+
|
|
263
|
+
Proxy environment variables alone are not an allowlist. An external gateway
|
|
264
|
+
uses named, process-only Workspace paths and verifies that they still point to
|
|
265
|
+
the configured directories; it does not create a virtual path mapping.
|
|
266
|
+
LittleGhost does not attest that an external proxy is ready or enforcing the
|
|
267
|
+
declared destinations. The application must supervise and verify that
|
|
268
|
+
gateway before giving a child network access.
|
|
269
|
+
|
|
270
|
+
These settings reach only processes launched through the Sandbox. Providers,
|
|
271
|
+
callbacks, and application Tool code still use the Ruby process's network
|
|
272
|
+
access.
|
|
273
|
+
|
|
274
|
+
Test the deployed kernel and filesystem, not only the configuration object.
|
|
275
|
+
Exercise denied reads and writes, runtime paths, child creation, direct sockets,
|
|
276
|
+
cancellation, limits, and cleanup before depending on the Sandbox to isolate
|
|
277
|
+
generated code.
|
|
278
|
+
|
|
279
|
+
Continue with [Code Mode](code_mode.md) to see how a model-authored interpreter
|
|
280
|
+
uses this setup while every Tool call stays in the parent Ruby process. For
|
|
281
|
+
exact setup, access, and cleanup behavior, see `LittleGhost::Workspace`,
|
|
282
|
+
`LittleGhost::Sandbox`, and `LittleGhost::Sandbox::Scope`.
|