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
@@ -0,0 +1,135 @@
1
+ # Return Structured Results and Send Rich Content
2
+
3
+ An Agent can return a checked Ruby value instead of prose. It can also receive
4
+ text alongside images and documents when the selected model supports them.
5
+
6
+ ## Declare a result shape
7
+
8
+ Use `result_schema` on the Agent:
9
+
10
+ ```ruby
11
+ class SupportTriageAgent < LittleGhost::Agent
12
+ model :customer_support
13
+ system_prompt "Classify the request using only the supplied evidence."
14
+
15
+ result_schema(
16
+ name: "support_triage",
17
+ description: "Routing decision for one support request",
18
+ type: "object",
19
+ properties: {
20
+ category: {
21
+ type: "string",
22
+ enum: %w[billing delivery returns other]
23
+ },
24
+ urgent: {type: "boolean"},
25
+ summary: {type: "string", maxLength: 500}
26
+ },
27
+ required: %w[category urgent summary],
28
+ additionalProperties: false
29
+ )
30
+ end
31
+
32
+ run = SupportTriageAgent.ask("My package is missing and I leave tomorrow.")
33
+
34
+ if run.completed?
35
+ triage = run.result.output
36
+ route_request(category: triage.fetch("category"), urgent: triage.fetch("urgent"))
37
+ else
38
+ report_failure(run.error)
39
+ end
40
+ ```
41
+
42
+ Every object in a result schema must set `additionalProperties: false` and list
43
+ every property in `required`. The top-level type must be `object`. LittleGhost
44
+ checks the supported JSON Schema subset when the Agent is defined and checks
45
+ the returned value again before publishing a `LittleGhost::StructuredResult`.
46
+
47
+ LittleGhost supports a focused subset of [JSON Schema Draft
48
+ 2020-12](https://json-schema.org/draft/2020-12/json-schema-core). The
49
+ `LittleGhost::Agent.result_schema` API reference lists the accepted keywords.
50
+
51
+ For a structured Agent, `run.result.output` returns the checked value. For an
52
+ ordinary Agent, it returns text. Use `run.result.structured_result` when you
53
+ also need the schema name.
54
+
55
+ ## Let LittleGhost choose the strategy
56
+
57
+ The default `strategy: :auto` uses provider-native structured output when it is
58
+ available and otherwise uses a terminal Tool when the model supports reliable
59
+ Tool calls. You can require one strategy:
60
+
61
+ ```ruby
62
+ result_schema(
63
+ {
64
+ type: "object",
65
+ properties: {answer: {type: "string"}},
66
+ required: ["answer"],
67
+ additionalProperties: false
68
+ },
69
+ name: "answer",
70
+ strategy: :provider
71
+ )
72
+ ```
73
+
74
+ Use `:provider` or `:tool` when your application depends on that exact path.
75
+ LittleGhost raises `LittleGhost::ConfigurationError` before execution if the
76
+ selected model cannot provide it.
77
+
78
+ If the first response is missing or invalid, LittleGhost asks the model to
79
+ repair it once. A second invalid response ends the Run with
80
+ `LittleGhost::StructuredResultError`.
81
+
82
+ > **Safety note:** A checked shape tells you that fields and types match the
83
+ > schema, not that the model's claims are correct. Apply your normal business
84
+ > checks before the result changes data, spends money, or contacts someone.
85
+
86
+ ## Combine text with an image
87
+
88
+ Build a user `Message` from typed content blocks:
89
+
90
+ ```ruby
91
+ image = LittleGhost::Content::Image.new(
92
+ data: File.binread("tmp/damaged-package.png"),
93
+ media_type: "image/png"
94
+ )
95
+
96
+ message = LittleGhost::Message.new(
97
+ role: :user,
98
+ content: [
99
+ LittleGhost::Content::Text.new(
100
+ text: "Describe the visible damage without guessing its cause."
101
+ ),
102
+ image
103
+ ]
104
+ )
105
+
106
+ run = DamageReviewAgent.ask(message)
107
+ ```
108
+
109
+ For a document, include a display name:
110
+
111
+ ```ruby
112
+ document = LittleGhost::Content::Document.new(
113
+ data: File.binread("tmp/refund-guide.pdf"),
114
+ media_type: "application/pdf",
115
+ name: "refund-guide.pdf"
116
+ )
117
+ ```
118
+
119
+ `Image` and `Document` hold the original bytes. Serialization base64-encodes
120
+ them when they cross a JSON boundary, which increases request size. The
121
+ resolved model checks the content type against its advertised input
122
+ capabilities before making the provider request.
123
+
124
+ > **Safety note:** When content comes from an upload, check its size and actual
125
+ > file type before creating the block. Send it only to a provider you use for
126
+ > that kind of data, and don't treat text extracted from a file as proof of
127
+ > identity or permission.
128
+
129
+ Content blocks may also accompany a Tool result. They become visible to the
130
+ model, so apply the same size and disclosure checks as you would for user
131
+ content.
132
+
133
+ Continue with [Compose Agents](assemblies.md) to route a checked result through
134
+ several participants, or [Tools](tools.md) when the result should call Ruby
135
+ code.
@@ -0,0 +1,329 @@
1
+ # Give agents tools
2
+
3
+ Tools turn an agent from a writer into a participant in your application. Each
4
+ Tool offers one named operation with checked input. Your Ruby code decides what
5
+ the operation may do.
6
+
7
+ Start with a narrow read:
8
+
9
+ ```ruby
10
+ class HelpCenterLookupTool < LittleGhost::Tool
11
+ description "Look up a help center entry by topic."
12
+ input_schema(
13
+ type: "object",
14
+ properties: {topic: {type: "string", enum: %w[refunds shipping]}},
15
+ required: ["topic"],
16
+ additionalProperties: false
17
+ )
18
+
19
+ def call(input)
20
+ {
21
+ "refunds" => "Refunds are available within 30 days of purchase.",
22
+ "shipping" => "Standard shipping takes three to five business days."
23
+ }.fetch(input.fetch("topic"))
24
+ end
25
+ end
26
+
27
+ class CustomerSupportAgent < LittleGhost::Agent
28
+ system_prompt "Check the help center before stating company guidance."
29
+ tools HelpCenterLookupTool
30
+ end
31
+ ```
32
+
33
+ The model sees the Tool's name, description, and input schema. When it chooses
34
+ the Tool, LittleGhost checks the arguments, calls `#call`, and returns the
35
+ result to the model. That loop can happen several times before the Agent writes
36
+ its final answer.
37
+
38
+ ## Declare one Tool or a collection
39
+
40
+ An Agent's `tools` declaration accepts one or more classes. Pass a Tool class
41
+ directly for one operation:
42
+
43
+ ```ruby
44
+ class CustomerSupportAgent < LittleGhost::Agent
45
+ tools HelpCenterLookupTool
46
+ end
47
+ ```
48
+
49
+ Pass several classes in one declaration, or use several declarations. They are
50
+ equivalent and inherited declarations are retained:
51
+
52
+ ```ruby
53
+ class CustomerSupportAgent < LittleGhost::Agent
54
+ tools HelpCenterLookupTool, OrderStatusTool
55
+ tools EscalateConversationTool
56
+ end
57
+ ```
58
+
59
+ For a related or dynamically discovered collection, pass a provider class that
60
+ implements `self.tools(binding)`. It may return Tool classes, Tool instances,
61
+ nested arrays, or `nil`; LittleGhost flattens the result and binds every Tool to
62
+ the current run:
63
+
64
+ ```ruby
65
+ class AccountTools
66
+ def self.tools(binding)
67
+ [
68
+ AccountStatusTool,
69
+ (CloseAccountTool if binding.run.invocation.context["may_close_account"])
70
+ ]
71
+ end
72
+ end
73
+
74
+ class CustomerSupportAgent < LittleGhost::Agent
75
+ tools HelpCenterLookupTool, AccountTools
76
+ end
77
+ ```
78
+
79
+ Prefer `available_if` on an individual Tool when only that operation is
80
+ conditional. Use a provider when the collection itself owns discovery,
81
+ construction, or shared setup for an application or remote service.
82
+
83
+ LittleGhost does not enforce class names. A useful application convention is
84
+ to end one model-callable operation with `Tool` and a provider of multiple
85
+ operations with `Tools`—for example, `OrderStatusTool` and `AccountTools`.
86
+ The suffix makes `tools AccountTools` readable without introducing a framework
87
+ base class or hiding ordinary Ruby composition.
88
+
89
+ ## Check permission in Ruby
90
+
91
+ A schema answers “Is this input shaped correctly?” It does not answer “May this
92
+ caller perform this operation?” Use values established by your application to
93
+ answer that second question.
94
+ For sensitive work, read those values inside the Tool:
95
+
96
+ ```ruby
97
+ class OrderStatusTool < LittleGhost::Tool
98
+ description "Look up an order for the current customer."
99
+ input_schema(
100
+ type: "object",
101
+ properties: {order_number: {type: "string"}},
102
+ required: ["order_number"],
103
+ additionalProperties: false
104
+ )
105
+
106
+ def call(input)
107
+ Orders.status_for(
108
+ actor_id: run.invocation.actor_id,
109
+ account_id: run.invocation.context.fetch("account_id"),
110
+ order_number: input.fetch("order_number")
111
+ )
112
+ end
113
+ end
114
+ ```
115
+
116
+ Here, the model chooses `order_number`. The application supplies `actor_id` and
117
+ `account_id` after authenticating the request. The Tool reads those values
118
+ through its run-scoped binding: the objects LittleGhost attaches to a Tool for
119
+ one execution. They are not part of the model's Tool arguments.
120
+
121
+ The Run also has `context.state`, mutable working state for this execution. When
122
+ saved conversations are configured, it may contain values from an earlier Run.
123
+ Recheck saved values before using them for a permission decision.
124
+
125
+ > **Safety note:** Keep identity and account membership in the Run's invocation,
126
+ > not in model-selected arguments. A valid Tool input may still name a record
127
+ > the current caller isn't allowed to use.
128
+
129
+ ## Know where a Tool runs
130
+
131
+ A local Tool is application code. Its `#call` method runs in the same Ruby
132
+ process as LittleGhost, with the same access as the rest of your application. A
133
+ Sandbox contains only work that the Tool explicitly sends through it.
134
+
135
+ Some Tools deliberately delegate a smaller operation to the Sandbox:
136
+
137
+ - `LittleGhost::Tools::Filesystem` reads and changes paths through the bound
138
+ Sandbox. It exposes mutation Tools only when that Sandbox is writable.
139
+ - `LittleGhost::Tools::Shell` runs one argument vector through the bound
140
+ Sandbox. It does not interpret shell syntax.
141
+ - An application Tool can send work through its bound `sandbox` when it needs
142
+ the same file and process restrictions. Its bound `workspace` names paths but
143
+ does not restrict access to them. Do not pass model-selected paths from
144
+ `Workspace#resolve` to `File`; use Sandbox file operations, which reject
145
+ symlinks while opening the path.
146
+
147
+ This distinction keeps the architecture predictable:
148
+
149
+ ```text
150
+ model ──arguments──> Tool#call ──> application service
151
+ │
152
+ └──> bound Sandbox ──> file or child process
153
+ ```
154
+
155
+ Provider requests also leave from the application process. Sandbox network
156
+ settings apply to processes launched through that Sandbox, not to model
157
+ providers, callbacks, or arbitrary Ruby inside a Tool.
158
+
159
+ Read [Workspaces and Sandboxes](sandboxing.md) before exposing filesystem or
160
+ process operations to model influence. It shows which paths and commands a
161
+ child process can access, how networking is restricted, and who cleans up the
162
+ resources.
163
+
164
+ ## Use the run-scoped binding
165
+
166
+ LittleGhost creates and binds fresh Tool instances for each Agent run. A Run is
167
+ one top-level Agent or Assembly execution. A Tool can reach that `run`, its
168
+ `agent`, the `runtime` that holds shared configuration and services, and the
169
+ current `workspace` and `sandbox` through accessors supplied by
170
+ `Tool::Binding`.
171
+
172
+ That binding carries application collaborators, not model arguments. Keep request
173
+ identity on `run.invocation`, working state on `context.state`, and the
174
+ model-selected input in the `input` passed to `#call`. Keeping those three
175
+ sources distinct makes permission checks easier to follow.
176
+
177
+ Registries close Tool instances that implement `#close`. Tool instance state
178
+ therefore belongs to one Agent run unless your Tool deliberately talks to a
179
+ shared application service.
180
+
181
+ Use `available_if` when the run itself determines whether an operation exists.
182
+ The predicate receives the same binding and runs before the Tool is constructed:
183
+
184
+ ```ruby
185
+ class UpdateSlackMessageTool < LittleGhost::Tool
186
+ available_if { |binding| binding.run.invocation.interface == "slack" }
187
+ end
188
+ ```
189
+
190
+ This controls discovery, not authorization. The Tool must still validate the
191
+ caller identity and account permissions supplied by the application.
192
+
193
+ ## Make concurrency and retries deliberate
194
+
195
+ LittleGhost may run independent Tool calls concurrently. Mark a Tool
196
+ `exclusive true` when it reads or changes shared mutable state that must not
197
+ overlap another exclusive Tool in the same run:
198
+
199
+ ```ruby
200
+ class UpdateDraftTool < LittleGhost::Tool
201
+ exclusive true
202
+ description "Replace one section of the current account's draft."
203
+ input_schema(
204
+ type: "object",
205
+ properties: {
206
+ section: {type: "string"},
207
+ content: {type: "string"}
208
+ },
209
+ required: %w[section content],
210
+ additionalProperties: false
211
+ )
212
+
213
+ def call(input)
214
+ Drafts.replace_section(
215
+ account_id: run.invocation.context.fetch("account_id"),
216
+ section: input.fetch("section"),
217
+ content: input.fetch("content")
218
+ )
219
+ end
220
+ end
221
+ ```
222
+
223
+ Retries can repeat a Tool call. Prefer read-only operations, idempotency keys,
224
+ or writes that are safe to apply more than once. Do not rely on the prompt to
225
+ prevent duplicate side effects.
226
+
227
+ Raise `LittleGhost::ToolError` for an expected failure the model can act on.
228
+ Its message is model-visible, so keep it safe to disclose. LittleGhost hides
229
+ unexpected exception messages from the model while retaining the original
230
+ error for trusted application inspection.
231
+
232
+ ## Return values and artifacts
233
+
234
+ A Tool normally returns one Ruby value. Application callers and code mode
235
+ receive that value, while LittleGhost serializes it for the model. Use
236
+ `Tool::Result` when the operation also produces files or media:
237
+
238
+ ```ruby
239
+ def call(input)
240
+ report = Reports.build(input.fetch("period"))
241
+ LittleGhost::Tool::Result.new(
242
+ value: {rows: report.rows.length},
243
+ artifacts: [
244
+ LittleGhost::Artifact.new(
245
+ data: report.csv,
246
+ media_type: "text/csv",
247
+ name: "report.csv"
248
+ )
249
+ ]
250
+ )
251
+ end
252
+ ```
253
+
254
+ `Tool::Result#value` is the same plain Ruby value a Tool would otherwise return.
255
+ Each inline `Artifact` has bytes, a MIME media type, and an optional name and
256
+ metadata. Use `Artifact.deferred(reference:, media_type:, ...)` when another
257
+ application service stores the bytes. The optional block passed to
258
+ `Configuration#artifacts` receives the deferred Artifact and may use its
259
+ application-defined `reference` to load the bytes later.
260
+
261
+ Artifact handling is opt-in for the Runtime:
262
+
263
+ ```ruby
264
+ LittleGhost.configure do |config|
265
+ config.workspace = {
266
+ provider: :directory,
267
+ root: "tmp/agent-runs",
268
+ paths: {artifacts: "artifacts"}
269
+ }
270
+ config.artifacts
271
+ end
272
+ ```
273
+
274
+ Once enabled, artifact handling covers three cases:
275
+
276
+ - Input images and documents are stored so filesystem Tools and code mode can
277
+ read the same bytes. The model still receives only the original image or
278
+ document.
279
+ - Tool artifacts are stored and returned to the model as images or documents.
280
+ The stored reference is shown only when the media cannot be included in the
281
+ model request.
282
+ - Oversized successful Tool values are stored automatically. The model receives
283
+ a short preview and a reference instead of the complete value.
284
+
285
+ The Tool's Ruby return value does not change. LittleGhost limits the size of
286
+ each stored file, the total files and bytes stored for a Run, and the media
287
+ sent in one model turn. If an oversized value cannot be stored within those
288
+ limits, the application still receives the complete Ruby value and the model
289
+ receives a short preview with a storage-limit notice.
290
+
291
+ To load a deferred artifact, pass a block. It may return bytes, an inline
292
+ Artifact with a final MIME type, or `nil` when the referenced file is no longer
293
+ available:
294
+
295
+ ```ruby
296
+ config.artifacts do |artifact, run:|
297
+ StoredFiles.read(artifact.reference, actor_id: run.invocation.actor_id)
298
+ end
299
+ ```
300
+
301
+ > **Safety note:** A deferred reference is data, not proof that the current
302
+ > caller may read a file. Check it against identity established by the
303
+ > application, restrict the storage service or network destination, and limit
304
+ > the bytes fetched before returning them. LittleGhost applies its storage limit
305
+ > after the resolver returns.
306
+
307
+ Declaring the named Workspace path does not grant model access. When the Agent
308
+ should read artifact references, grant the Sandbox read access to the
309
+ `:artifacts` path and include a filesystem Tool. The model can list that path
310
+ when a task genuinely needs a stored file; ordinary multimodal work does not
311
+ need a second reference to media already in the conversation.
312
+
313
+ ## Let code mode compose the same capabilities
314
+
315
+ Without code mode, the model chooses one Tool operation and LittleGhost returns
316
+ the result before the model chooses the next step. Code mode lets the model
317
+ write a small Ruby program that calls several of the same Tools, combines their
318
+ results, and returns one useful value.
319
+
320
+ Each call crosses back to the parent Ruby process. LittleGhost checks the schema
321
+ and calls the Tool method there, so permission checks inside `#call` still
322
+ apply. Code mode receives the Tool's Ruby return value, while artifacts return
323
+ with the surrounding `exec` or `wait` result. Tool limits, callbacks, events,
324
+ and tracing work the same way for direct and code-mode calls.
325
+
326
+ Continue with [Structured Results and Content](structured_outputs_and_content.md)
327
+ to give Agent responses a predictable shape and accept images or documents.
328
+ For exact Tool DSL and result contracts, see `LittleGhost::Tool` and
329
+ `LittleGhost::Tool::Binding`.
@@ -18,13 +18,13 @@ module LittleGhost
18
18
  # The adapter has no state between #stream calls, so one instance can
19
19
  # translate independent runs.
20
20
  #
21
- # === Security and trust
21
+ # === Choose what the interface receives
22
22
  #
23
23
  # Provider plaintext reasoning becomes AG-UI reasoning events. Tool
24
24
  # arguments and results, invocation metadata, subagent events, trace context,
25
25
  # and selected error text also pass through without redaction. Authorize and
26
26
  # filter the complete stream before transport, and send it only to an
27
- # interface trusted to display that data. Encrypted reasoning and provider
27
+ # interface intended to display that data. Encrypted reasoning and provider
28
28
  # continuity artifacts are never exposed here.
29
29
  class Adapter
30
30
  TERMINAL_EVENTS = %i[run_partial run_cancel run_stop run_error].freeze # :nodoc:
@@ -22,7 +22,7 @@ module LittleGhost
22
22
  #
23
23
  # Tool overrides must be classes. A delegated agent otherwise receives only
24
24
  # its own declared tools; it does not inherit the parent's registry. The
25
- # manager enforces its concurrency, identity, polling, and persistence bounds.
25
+ # manager enforces concurrency, identity, wait duration, and persistence bounds.
26
26
  module Delegation
27
27
  def self.included(base) # :nodoc:
28
28
  base.extend(ClassMethods)
@@ -40,7 +40,7 @@ module LittleGhost
40
40
  # subagent_long_poll_duration() -> Float
41
41
  # subagent_long_poll_duration(seconds) -> Float
42
42
  #
43
- # The maximum long-poll duration used by subagent wait tools.
43
+ # How long +wait_for_subagents+ watches for progress before returning.
44
44
  #
45
45
  # The default comes from Subagents::Manager. Values must be positive,
46
46
  # finite numbers and are normalized to Float.
@@ -36,7 +36,12 @@ module LittleGhost
36
36
  end
37
37
  paths ||= binding.run.runtime.skill_paths
38
38
  catalog = LittleGhost::Skills::Catalog.new(
39
- **configuration.merge(paths:, resource_root: binding.run&.runtime&.skill_resource_root)
39
+ **configuration.merge(
40
+ paths:,
41
+ resource_root: binding.run&.runtime&.skill_resource_root,
42
+ workspace: binding.workspace,
43
+ sandbox: binding.sandbox
44
+ )
40
45
  )
41
46
  return [] if catalog.names.empty?
42
47
 
@@ -110,6 +110,7 @@ module LittleGhost
110
110
 
111
111
  def track_tool_call(payload, context: nil)
112
112
  tool_use = payload.fetch(:tool_use)
113
+ return Support::Callbacks.continue if code_mode_control_tool?(tool_use)
113
114
  return Support::Callbacks.continue if @tool_loop_except.include?(tool_use.name)
114
115
 
115
116
  key = [tool_use.name, tool_loop_digest(tool_use.input)]
@@ -173,11 +174,14 @@ module LittleGhost
173
174
  tool_name: tool_use.name,
174
175
  count: count
175
176
  )
177
+ warned_content = "#{warning}\n\n#{result.content}"
176
178
  replacement = Tool::ExecutionResult.new(
177
- content: "#{warning}\n\n#{result.content}",
179
+ value: result.value,
180
+ content: warned_content,
178
181
  status: result.status,
179
182
  error: result.error,
180
- companion_content: result.companion_content
183
+ artifacts: result.artifacts,
184
+ presentation_content: result.presentation_content
181
185
  )
182
186
  Support::Callbacks.replace(payload.merge(result: replacement))
183
187
  end