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.
Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +28 -6
  3. data/docs/guides/assemblies.md +143 -17
  4. data/docs/guides/code_mode.md +275 -0
  5. data/docs/guides/core_concepts.md +46 -30
  6. data/docs/guides/getting_started.md +17 -6
  7. data/docs/guides/integrations.md +217 -0
  8. data/docs/guides/models_and_providers.md +125 -0
  9. data/docs/guides/production.md +81 -15
  10. data/docs/guides/prompt_views.md +14 -7
  11. data/docs/guides/sandboxing.md +282 -0
  12. data/docs/guides/skills.md +141 -0
  13. data/docs/guides/structured_outputs_and_content.md +135 -0
  14. data/docs/guides/tools.md +329 -0
  15. data/lib/little_ghost/ag_ui/adapter.rb +2 -2
  16. data/lib/little_ghost/agent/delegation.rb +2 -2
  17. data/lib/little_ghost/agent/skills.rb +6 -1
  18. data/lib/little_ghost/agent/tool_loop.rb +6 -2
  19. data/lib/little_ghost/agent.rb +248 -46
  20. data/lib/little_ghost/agent_builder.rb +61 -22
  21. data/lib/little_ghost/agent_stream_source.rb +262 -0
  22. data/lib/little_ghost/artifact.rb +182 -0
  23. data/lib/little_ghost/artifacts/presentation_budget.rb +44 -0
  24. data/lib/little_ghost/artifacts/workspace_store.rb +370 -0
  25. data/lib/little_ghost/artifacts.rb +11 -0
  26. data/lib/little_ghost/assembly.rb +63 -17
  27. data/lib/little_ghost/assembly_builder.rb +26 -26
  28. data/lib/little_ghost/assembly_execution.rb +23 -2
  29. data/lib/little_ghost/code_mode/broker.rb +164 -0
  30. data/lib/little_ghost/code_mode/catalog.rb +54 -0
  31. data/lib/little_ghost/code_mode/engine.rb +58 -0
  32. data/lib/little_ghost/code_mode/javascript/catalog.rb +118 -0
  33. data/lib/little_ghost/code_mode/javascript/client.rb +550 -0
  34. data/lib/little_ghost/code_mode/javascript/host.rb +347 -0
  35. data/lib/little_ghost/code_mode/javascript/session.rb +579 -0
  36. data/lib/little_ghost/code_mode/javascript_engine.rb +172 -0
  37. data/lib/little_ghost/code_mode/protocol.rb +78 -0
  38. data/lib/little_ghost/code_mode/ruby/catalog.rb +80 -0
  39. data/lib/little_ghost/code_mode/ruby/host.rb +102 -0
  40. data/lib/little_ghost/code_mode/ruby/session.rb +508 -0
  41. data/lib/little_ghost/code_mode/ruby_engine.rb +86 -0
  42. data/lib/little_ghost/code_mode/runtime.rb +335 -0
  43. data/lib/little_ghost/code_mode/session.rb +61 -0
  44. data/lib/little_ghost/code_mode/types.rb +58 -0
  45. data/lib/little_ghost/code_mode.rb +53 -0
  46. data/lib/little_ghost/configuration.rb +107 -12
  47. data/lib/little_ghost/content.rb +19 -8
  48. data/lib/little_ghost/data_map.rb +2 -2
  49. data/lib/little_ghost/errors.rb +26 -7
  50. data/lib/little_ghost/execution.rb +1 -2
  51. data/lib/little_ghost/graph.rb +367 -207
  52. data/lib/little_ghost/mcp/client.rb +487 -88
  53. data/lib/little_ghost/mcp/toolset.rb +210 -0
  54. data/lib/little_ghost/mcp/types.rb +216 -0
  55. data/lib/little_ghost/mcp.rb +3 -0
  56. data/lib/little_ghost/model_capabilities.rb +9 -6
  57. data/lib/little_ghost/model_request.rb +0 -12
  58. data/lib/little_ghost/model_resolver.rb +13 -3
  59. data/lib/little_ghost/model_response.rb +3 -7
  60. data/lib/little_ghost/network/authorizer_server.rb +162 -0
  61. data/lib/little_ghost/network/certificate_authority.rb +98 -0
  62. data/lib/little_ghost/network/envoy_config.rb +362 -0
  63. data/lib/little_ghost/network/envoy_gateway.rb +409 -0
  64. data/lib/little_ghost/network/external_gateway.rb +68 -0
  65. data/lib/little_ghost/network.rb +96 -0
  66. data/lib/little_ghost/prompt_resolver.rb +7 -7
  67. data/lib/little_ghost/provider_registry.rb +3 -3
  68. data/lib/little_ghost/providers/anthropic.rb +8 -1
  69. data/lib/little_ghost/providers/gemini.rb +10 -1
  70. data/lib/little_ghost/providers/vertex_ai.rb +6 -1
  71. data/lib/little_ghost/run.rb +83 -13
  72. data/lib/little_ghost/run_context.rb +9 -0
  73. data/lib/little_ghost/run_result.rb +0 -7
  74. data/lib/little_ghost/runtime/hook.rb +5 -0
  75. data/lib/little_ghost/runtime/hooks/artifacts.rb +338 -0
  76. data/lib/little_ghost/runtime.rb +102 -34
  77. data/lib/little_ghost/sandbox/capabilities.rb +68 -0
  78. data/lib/little_ghost/sandbox/environment_policy.rb +41 -0
  79. data/lib/little_ghost/sandbox/filesystem.rb +377 -0
  80. data/lib/little_ghost/sandbox/isolated_backend.rb +163 -0
  81. data/lib/little_ghost/sandbox/limits.rb +48 -0
  82. data/lib/little_ghost/sandbox/mount.rb +129 -0
  83. data/lib/little_ghost/sandbox/network_policy.rb +108 -0
  84. data/lib/little_ghost/sandbox/policy.rb +88 -0
  85. data/lib/little_ghost/sandbox/process_runner.rb +103 -0
  86. data/lib/little_ghost/sandbox/process_session.rb +335 -0
  87. data/lib/little_ghost/sandbox/scope.rb +304 -0
  88. data/lib/little_ghost/sandbox.rb +158 -32
  89. data/lib/little_ghost/sandboxes/bubblewrap.rb +351 -0
  90. data/lib/little_ghost/sandboxes/native.rb +51 -0
  91. data/lib/little_ghost/sandboxes/seatbelt.rb +261 -0
  92. data/lib/little_ghost/sandboxes/unrestricted.rb +241 -0
  93. data/lib/little_ghost/session.rb +30 -6
  94. data/lib/little_ghost/skills/catalog.rb +94 -14
  95. data/lib/little_ghost/skills/resource_root.rb +42 -0
  96. data/lib/little_ghost/skills/skill.rb +0 -3
  97. data/lib/little_ghost/stream_event.rb +8 -13
  98. data/lib/little_ghost/subagents/control_tool.rb +8 -0
  99. data/lib/little_ghost/subagents/manager.rb +12 -9
  100. data/lib/little_ghost/support/callbacks.rb +3 -1
  101. data/lib/little_ghost/support/content_capture.rb +3 -3
  102. data/lib/little_ghost/support/http_client.rb +2 -2
  103. data/lib/little_ghost/support/redactor.rb +1 -1
  104. data/lib/little_ghost/tool.rb +104 -53
  105. data/lib/little_ghost/tool_registry.rb +1 -1
  106. data/lib/little_ghost/tools/filesystem.rb +1 -1
  107. data/lib/little_ghost/tools/shell.rb +4 -3
  108. data/lib/little_ghost/version.rb +1 -1
  109. data/lib/little_ghost/workspace.rb +222 -8
  110. data/lib/little_ghost.rb +21 -12
  111. metadata +98 -2
  112. data/lib/little_ghost/unrestricted_sandbox.rb +0 -306
@@ -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, tool results, and attachments may leave the application for the selected external provider. Select providers from trusted configuration and account for their retention and data-residency policies.
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
- ## Treat tools as application boundaries
143
+ ## Keep Tool permission checks in application code
134
144
 
135
- A tool schema checks the shape of model-supplied input. Your application still owns permission checks, safe retries, rate limits, tenant boundaries, and auditing.
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 from `run.invocation.context`. Do not make permission decisions from model arguments.
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 gives one Run a place for files. A Sandbox decides how filesystem and process operations happen there.
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
- `LittleGhost::UnrestrictedSandbox` uses the host machine with the Ruby process's permissions. It does not contain untrusted code. Expose only the tools the model needs, and use a real isolation boundary when untrusted code must run.
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
- The Run closes workspaces and sandboxes that LittleGhost creates for it. If your application passes an existing instance instead, your application keeps ownership and must close it when its own lifecycle ends.
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
- ## Instrument without leaking the application
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 data. Redact sensitive values before they leave your boundary. Avoid attributes with many unique values, such as raw order or request IDs. Replacing one identifier does not make the rest of the data anonymous.
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. Keep detailed provider errors and sensitive diagnostics in trusted monitoring channels, not in model or user responses.
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
- ## Keep ownership and failure visible
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, event delivery, or instrumentation can still raise an exception: once those boundaries fail, LittleGhost cannot promise a clean ending.
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 is an independent configuration snapshot. It does not replace LittleGhost's shared default.
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 its own Run, participants, Tools, and Runtime-created workspace and sandbox. An Agent or Assembly already bound to an active Run must stay with that Run.
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 Session. Multi-process deployments need coordination from their store. Custom stores, identity and credential resolvers, model resolvers, hooks, instrumentation subscribers, providers, and resource factories may receive concurrent calls and must be thread-safe.
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
 
@@ -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 easy to scan. It also gives shared instructions and application values a natural home.
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
- ## Override the convention when it helps
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`. Earlier roots win, which is useful when one trusted application layer overrides a shared prompt package.
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. They can call Ruby, so keep every prompt directory application-controlled and non-user-writable. Never turn a request or model-supplied path into a prompt root.
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` exists for the uncommon case where trusted application code selects a request-specific root. It records a trust decision; it does not make an untrusted directory safe.
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
- ## Keep request composition separate
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 [Running in Production](production.md) to configure model roles, preserve sessions, supervise execution, and connect observability.
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.