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
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
|
|
|
@@ -116,6 +118,14 @@ end
|
|
|
116
118
|
record_outcome(run.outcome, error_type: run.error&.class&.name)
|
|
117
119
|
```
|
|
118
120
|
|
|
121
|
+
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.
|
|
122
|
+
|
|
123
|
+
> **Safety note:** Contextual events can include inputs, reasoning, Tool
|
|
124
|
+
> arguments and results, errors, and output from every participant. Check that
|
|
125
|
+
> the destination may see the complete Run, or filter the events before sending
|
|
126
|
+
> or storing them. LittleGhost's AG-UI adapter ignores these events unless the
|
|
127
|
+
> application translates them explicitly.
|
|
128
|
+
|
|
119
129
|
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
130
|
|
|
121
131
|
```ruby
|
|
@@ -130,11 +140,16 @@ execution.run.completed?
|
|
|
130
140
|
|
|
131
141
|
The event block runs on the worker thread, so keep it quick. Cancellation, deadlines, and `close` ask the work to stop; they cannot forcibly end arbitrary provider or tool code. They also cannot undo actions that already happened.
|
|
132
142
|
|
|
133
|
-
##
|
|
143
|
+
## Keep Tool permission checks in application code
|
|
134
144
|
|
|
135
|
-
A
|
|
145
|
+
A Tool schema checks the shape of model-supplied input. Your application still
|
|
146
|
+
owns permission checks, safe retries, rate limits, and auditing. An ordinary
|
|
147
|
+
Tool runs in the Ruby process; a Sandbox contains only file or child-process
|
|
148
|
+
work that deliberately passes through it.
|
|
136
149
|
|
|
137
|
-
Use the Tool binding's `run` to read current, application-established values
|
|
150
|
+
Use the Tool binding's `run` to read current, application-established values
|
|
151
|
+
from `run.invocation.context`. Don't rely on model arguments for identity or
|
|
152
|
+
account membership.
|
|
138
153
|
|
|
139
154
|
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
155
|
|
|
@@ -142,27 +157,72 @@ A `ToolError` message is visible to the model, so keep it safe to share. LittleG
|
|
|
142
157
|
|
|
143
158
|
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
159
|
|
|
160
|
+
[Tools](tools.md) develops this pattern from the first application Tool through
|
|
161
|
+
bindings, concurrency, sandbox delegation, and code mode.
|
|
162
|
+
|
|
145
163
|
## Choose workspace and sandbox behavior explicitly
|
|
146
164
|
|
|
147
|
-
A Workspace
|
|
165
|
+
A **Workspace** names the host paths associated with a Run. A **Sandbox**
|
|
166
|
+
controls filesystem operations and child processes deliberately sent through
|
|
167
|
+
it. A custom Ruby Tool stays in the application process unless it delegates
|
|
168
|
+
work to the bound Sandbox.
|
|
169
|
+
|
|
170
|
+
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:
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
require "fileutils"
|
|
174
|
+
require "tmpdir"
|
|
175
|
+
|
|
176
|
+
LittleGhost.configure do |config|
|
|
177
|
+
config.workspace = lambda do |**|
|
|
178
|
+
root = Dir.mktmpdir("little-ghost-support-")
|
|
179
|
+
LittleGhost::Workspace.new(
|
|
180
|
+
root:,
|
|
181
|
+
teardown: lambda do |workspace:, **|
|
|
182
|
+
FileUtils.remove_entry_secure(workspace.root) if File.exist?(workspace.root)
|
|
183
|
+
end
|
|
184
|
+
)
|
|
185
|
+
end
|
|
186
|
+
config.sandbox = {
|
|
187
|
+
provider: :native,
|
|
188
|
+
files: {root: :read_write},
|
|
189
|
+
root_filesystem: :isolated,
|
|
190
|
+
environment: {inherit: false, set: {"LANG" => "C.UTF-8"}},
|
|
191
|
+
network: :none
|
|
192
|
+
}
|
|
193
|
+
end
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
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.
|
|
197
|
+
|
|
198
|
+
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
199
|
|
|
149
|
-
|
|
200
|
+
[Workspaces and Sandboxes](sandboxing.md) explains backend selection, logical
|
|
201
|
+
Workspace paths, files, process-only runtime paths, Scopes, filtered
|
|
202
|
+
networking, and deployment validation in depth.
|
|
150
203
|
|
|
151
|
-
|
|
204
|
+
[Code Mode](code_mode.md) applies that setup to model-authored Ruby or
|
|
205
|
+
optional JavaScript that composes the Agent's existing Tools.
|
|
152
206
|
|
|
153
|
-
##
|
|
207
|
+
## Protect data in telemetry
|
|
154
208
|
|
|
155
209
|
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
210
|
|
|
157
|
-
An external telemetry service may receive application identifiers and event
|
|
211
|
+
An external telemetry service may receive application identifiers and event
|
|
212
|
+
data. Redact sensitive values before exporting them. Avoid attributes with many
|
|
213
|
+
unique values, such as raw order or request IDs.
|
|
158
214
|
|
|
159
|
-
A composite `RunResult` includes short step summaries and trajectory queries.
|
|
215
|
+
A composite `RunResult` includes short step summaries and trajectory queries.
|
|
216
|
+
Keep detailed provider errors and sensitive diagnostics in your monitoring
|
|
217
|
+
system, not in model or user responses.
|
|
160
218
|
|
|
161
|
-
##
|
|
219
|
+
## Know what the Run closes and raises
|
|
162
220
|
|
|
163
221
|
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
222
|
|
|
165
|
-
Ordinary execution failures appear on the Run and its final event. Cleanup,
|
|
223
|
+
Ordinary execution failures appear on the Run and its final event. Cleanup,
|
|
224
|
+
event delivery, or instrumentation can still raise an exception when
|
|
225
|
+
LittleGhost cannot promise a clean ending.
|
|
166
226
|
|
|
167
227
|
## Advanced: work with Runtime directly
|
|
168
228
|
|
|
@@ -176,11 +236,17 @@ runtime = LittleGhost::Runtime.new(configuration: configuration)
|
|
|
176
236
|
agent = CustomerSupportAgent.new(runtime: runtime)
|
|
177
237
|
```
|
|
178
238
|
|
|
179
|
-
An explicit Runtime
|
|
239
|
+
An explicit Runtime has its own independent configuration. It does not replace
|
|
240
|
+
LittleGhost's shared default.
|
|
180
241
|
|
|
181
|
-
One Runtime can serve independent calls from several threads. Each call gets
|
|
242
|
+
One Runtime can serve independent calls from several threads. Each call gets
|
|
243
|
+
its own Run, participants, Tools, and Runtime-created Workspace and Sandbox. An
|
|
244
|
+
Agent or Assembly already bound to an active Run must stay with that Run.
|
|
182
245
|
|
|
183
|
-
Within one SessionStore instance, LittleGhost serializes calls sharing a
|
|
246
|
+
Within one SessionStore instance, LittleGhost serializes calls sharing a
|
|
247
|
+
Session. Multi-process deployments need coordination from their store. Custom
|
|
248
|
+
stores and other shared extension objects may receive concurrent calls and
|
|
249
|
+
must be thread-safe.
|
|
184
250
|
|
|
185
251
|
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
252
|
|
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`.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Give Agents Skills
|
|
2
|
+
|
|
3
|
+
A Skill is a reusable instruction package that an Agent can discover by name
|
|
4
|
+
and load when a task needs it. Skills keep specialized procedures out of the
|
|
5
|
+
Agent's main prompt.
|
|
6
|
+
|
|
7
|
+
## Write one focused skill
|
|
8
|
+
|
|
9
|
+
Create `app/skills/refund_guide/SKILL.md`:
|
|
10
|
+
|
|
11
|
+
```markdown
|
|
12
|
+
---
|
|
13
|
+
name: refund_guide
|
|
14
|
+
description: Determine which refund guidance applies to a customer request.
|
|
15
|
+
allowed-tools:
|
|
16
|
+
- help_center_lookup
|
|
17
|
+
compatibility: Requires the refunds help-center collection.
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Apply the refund guidance
|
|
21
|
+
|
|
22
|
+
1. Load the current refund entry with `help_center_lookup`.
|
|
23
|
+
2. Compare the purchase date and item category with the returned guidance.
|
|
24
|
+
3. State which facts support the decision and which facts are still missing.
|
|
25
|
+
4. Return the analysis to the application instead of issuing a refund.
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Each immediate child of a configured skill root may contain one `SKILL.md`.
|
|
29
|
+
Front matter requires a single-line `name` and `description`. Names contain
|
|
30
|
+
only letters, numbers, underscores, and hyphens. `allowed-tools` and
|
|
31
|
+
`compatibility` are optional information shown to the model.
|
|
32
|
+
|
|
33
|
+
A Skill may also include supporting files in `references/`, `scripts/`, or
|
|
34
|
+
`assets/`. Keep the main instructions short and point to a resource only when
|
|
35
|
+
the task needs it.
|
|
36
|
+
|
|
37
|
+
## Let an Agent discover skills
|
|
38
|
+
|
|
39
|
+
The conventional root is `app/skills`:
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
class CustomerSupportAgent < LittleGhost::Agent
|
|
43
|
+
system_prompt "Use an available skill when a request needs a documented procedure."
|
|
44
|
+
tools HelpCenterLookupTool
|
|
45
|
+
skills
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
At runtime, the Agent receives a short catalog of skill names, descriptions,
|
|
50
|
+
and locations. The generated `skills` Tool loads the full instructions when
|
|
51
|
+
the model selects one. An empty catalog adds nothing to the prompt and no Tool.
|
|
52
|
+
|
|
53
|
+
You can use another application-owned directory or expose only selected
|
|
54
|
+
skills:
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
class CustomerSupportAgent < LittleGhost::Agent
|
|
58
|
+
skills paths: [File.expand_path("../../support_skills", __dir__)],
|
|
59
|
+
only: %w[refund_guide]
|
|
60
|
+
end
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Paths may also come from a callable resolved for each Run. Build the path from
|
|
64
|
+
application configuration, not from a path supplied by the model or request.
|
|
65
|
+
|
|
66
|
+
## Pair instructions with Tools
|
|
67
|
+
|
|
68
|
+
`allowed-tools` tells the model which Tools a Skill expects. It doesn't add or
|
|
69
|
+
enable those Tools. The Agent's `tools` declaration remains the source of what
|
|
70
|
+
the model can call, and each Tool applies its normal application checks.
|
|
71
|
+
|
|
72
|
+
This separation makes Skills safe to use as guidance: changing a Markdown file
|
|
73
|
+
can change what the Agent tries, but it can't grant a new Tool, provider,
|
|
74
|
+
filesystem path, or credential.
|
|
75
|
+
|
|
76
|
+
> **Safety note:** Keep configured skill directories under application control
|
|
77
|
+
> and review Skill changes as prompt changes. A Skill can contain mistaken or
|
|
78
|
+
> outdated instructions, so application code should continue to check any
|
|
79
|
+
> operation that has side effects.
|
|
80
|
+
|
|
81
|
+
## Make resources available to code mode
|
|
82
|
+
|
|
83
|
+
Ordinary Skill loading needs no Workspace configuration. If model-authored code
|
|
84
|
+
also needs to read Skill resources, give those resources a stable model-visible
|
|
85
|
+
location:
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
require "fileutils"
|
|
89
|
+
require "tmpdir"
|
|
90
|
+
|
|
91
|
+
skill_root = File.expand_path("../../app/skills", __dir__)
|
|
92
|
+
|
|
93
|
+
LittleGhost.configure do |config|
|
|
94
|
+
config.skill_paths = [skill_root]
|
|
95
|
+
config.skill_resource_root = "workspace://skills"
|
|
96
|
+
|
|
97
|
+
config.workspace = lambda do |**|
|
|
98
|
+
root = Dir.mktmpdir("little-ghost-skills-")
|
|
99
|
+
LittleGhost::Workspace.new(
|
|
100
|
+
root:,
|
|
101
|
+
paths: {skills: skill_root},
|
|
102
|
+
teardown: lambda do |workspace:, **|
|
|
103
|
+
FileUtils.remove_entry_secure(workspace.root) if File.exist?(workspace.root)
|
|
104
|
+
end
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
config.sandbox = {
|
|
109
|
+
provider: :native,
|
|
110
|
+
files: {root: :read_write, skills: :read_only},
|
|
111
|
+
root_filesystem: :isolated,
|
|
112
|
+
network: :none
|
|
113
|
+
}
|
|
114
|
+
end
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The named Workspace path maps the configured Skill directory, and the Sandbox
|
|
118
|
+
makes it readable without allowing model-authored code to change it. The model
|
|
119
|
+
can then ask the Filesystem Tool for a path such as
|
|
120
|
+
`workspace://skills/refund_guide/references/example.md` without learning the
|
|
121
|
+
host's directory layout. [Workspaces and Sandboxes](sandboxing.md) explains how
|
|
122
|
+
to adapt the temporary root, backend, and other grants for your application.
|
|
123
|
+
|
|
124
|
+
## Write instructions that stand on their own
|
|
125
|
+
|
|
126
|
+
A useful Skill tells the Agent:
|
|
127
|
+
|
|
128
|
+
- When the procedure applies.
|
|
129
|
+
- Which information it needs before deciding.
|
|
130
|
+
- Which named Tools or resources help.
|
|
131
|
+
- What result the caller expects.
|
|
132
|
+
- Which action remains with the application.
|
|
133
|
+
- What to do when information is missing or conflicts.
|
|
134
|
+
|
|
135
|
+
Use direct instructions and stable domain language. Avoid repeating the
|
|
136
|
+
Agent's general prompt or relying on Tool behavior that the Tool description
|
|
137
|
+
doesn't promise.
|
|
138
|
+
|
|
139
|
+
Continue with [Workspaces and Sandboxes](sandboxing.md) when model-authored code
|
|
140
|
+
needs Skill resources. See [Tools](tools.md) for Tool validation, bindings, and
|
|
141
|
+
side effects.
|