little_ghost 0.3.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 (122) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +90 -84
  3. data/docs/guides/assemblies.md +412 -0
  4. data/docs/guides/code_mode.md +275 -0
  5. data/docs/guides/core_concepts.md +150 -239
  6. data/docs/guides/getting_started.md +125 -87
  7. data/docs/guides/integrations.md +217 -0
  8. data/docs/guides/models_and_providers.md +125 -0
  9. data/docs/guides/production.md +253 -0
  10. data/docs/guides/prompt_views.md +139 -0
  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 +5 -5
  16. data/lib/little_ghost/agent/delegation.rb +3 -3
  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 +411 -214
  20. data/lib/little_ghost/agent_builder.rb +61 -22
  21. data/lib/little_ghost/{agent_interruptions.rb → agent_interjections.rb} +12 -12
  22. data/lib/little_ghost/agent_stream_source.rb +262 -0
  23. data/lib/little_ghost/artifact.rb +182 -0
  24. data/lib/little_ghost/artifacts/presentation_budget.rb +44 -0
  25. data/lib/little_ghost/artifacts/workspace_store.rb +370 -0
  26. data/lib/little_ghost/artifacts.rb +11 -0
  27. data/lib/little_ghost/assembly.rb +110 -30
  28. data/lib/little_ghost/assembly_builder.rb +59 -21
  29. data/lib/little_ghost/assembly_execution.rb +110 -6
  30. data/lib/little_ghost/code_mode/broker.rb +164 -0
  31. data/lib/little_ghost/code_mode/catalog.rb +54 -0
  32. data/lib/little_ghost/code_mode/engine.rb +58 -0
  33. data/lib/little_ghost/code_mode/javascript/catalog.rb +118 -0
  34. data/lib/little_ghost/code_mode/javascript/client.rb +550 -0
  35. data/lib/little_ghost/code_mode/javascript/host.rb +347 -0
  36. data/lib/little_ghost/code_mode/javascript/session.rb +579 -0
  37. data/lib/little_ghost/code_mode/javascript_engine.rb +172 -0
  38. data/lib/little_ghost/code_mode/protocol.rb +78 -0
  39. data/lib/little_ghost/code_mode/ruby/catalog.rb +80 -0
  40. data/lib/little_ghost/code_mode/ruby/host.rb +102 -0
  41. data/lib/little_ghost/code_mode/ruby/session.rb +508 -0
  42. data/lib/little_ghost/code_mode/ruby_engine.rb +86 -0
  43. data/lib/little_ghost/code_mode/runtime.rb +335 -0
  44. data/lib/little_ghost/code_mode/session.rb +61 -0
  45. data/lib/little_ghost/code_mode/types.rb +58 -0
  46. data/lib/little_ghost/code_mode.rb +53 -0
  47. data/lib/little_ghost/configuration.rb +366 -47
  48. data/lib/little_ghost/content.rb +24 -13
  49. data/lib/little_ghost/data_map.rb +209 -0
  50. data/lib/little_ghost/errors.rb +28 -9
  51. data/lib/little_ghost/execution.rb +33 -34
  52. data/lib/little_ghost/graph.rb +376 -197
  53. data/lib/little_ghost/mcp/client.rb +487 -88
  54. data/lib/little_ghost/mcp/toolset.rb +210 -0
  55. data/lib/little_ghost/mcp/types.rb +216 -0
  56. data/lib/little_ghost/mcp.rb +3 -0
  57. data/lib/little_ghost/message.rb +4 -4
  58. data/lib/little_ghost/model_capabilities.rb +9 -6
  59. data/lib/little_ghost/model_request.rb +0 -12
  60. data/lib/little_ghost/model_resolver.rb +15 -5
  61. data/lib/little_ghost/model_response.rb +3 -7
  62. data/lib/little_ghost/network/authorizer_server.rb +162 -0
  63. data/lib/little_ghost/network/certificate_authority.rb +98 -0
  64. data/lib/little_ghost/network/envoy_config.rb +362 -0
  65. data/lib/little_ghost/network/envoy_gateway.rb +409 -0
  66. data/lib/little_ghost/network/external_gateway.rb +68 -0
  67. data/lib/little_ghost/network.rb +96 -0
  68. data/lib/little_ghost/prompt_resolver.rb +9 -7
  69. data/lib/little_ghost/provider_registry.rb +3 -3
  70. data/lib/little_ghost/providers/anthropic.rb +8 -1
  71. data/lib/little_ghost/providers/gemini.rb +10 -1
  72. data/lib/little_ghost/providers/vertex_ai.rb +6 -1
  73. data/lib/little_ghost/run.rb +162 -54
  74. data/lib/little_ghost/run_context.rb +42 -20
  75. data/lib/little_ghost/run_result.rb +0 -7
  76. data/lib/little_ghost/runtime/hook.rb +8 -3
  77. data/lib/little_ghost/runtime/hooks/artifacts.rb +338 -0
  78. data/lib/little_ghost/runtime.rb +160 -52
  79. data/lib/little_ghost/sandbox/capabilities.rb +68 -0
  80. data/lib/little_ghost/sandbox/environment_policy.rb +41 -0
  81. data/lib/little_ghost/sandbox/filesystem.rb +377 -0
  82. data/lib/little_ghost/sandbox/isolated_backend.rb +163 -0
  83. data/lib/little_ghost/sandbox/limits.rb +48 -0
  84. data/lib/little_ghost/sandbox/mount.rb +129 -0
  85. data/lib/little_ghost/sandbox/network_policy.rb +108 -0
  86. data/lib/little_ghost/sandbox/policy.rb +88 -0
  87. data/lib/little_ghost/sandbox/process_runner.rb +103 -0
  88. data/lib/little_ghost/sandbox/process_session.rb +335 -0
  89. data/lib/little_ghost/sandbox/scope.rb +304 -0
  90. data/lib/little_ghost/sandbox.rb +158 -32
  91. data/lib/little_ghost/sandboxes/bubblewrap.rb +351 -0
  92. data/lib/little_ghost/sandboxes/native.rb +51 -0
  93. data/lib/little_ghost/sandboxes/seatbelt.rb +261 -0
  94. data/lib/little_ghost/sandboxes/unrestricted.rb +241 -0
  95. data/lib/little_ghost/session.rb +39 -26
  96. data/lib/little_ghost/session_store.rb +9 -5
  97. data/lib/little_ghost/session_stores/agent_core_memory.rb +64 -56
  98. data/lib/little_ghost/session_stores/filesystem.rb +261 -0
  99. data/lib/little_ghost/session_stores/memory.rb +7 -0
  100. data/lib/little_ghost/skills/catalog.rb +94 -14
  101. data/lib/little_ghost/skills/resource_root.rb +42 -0
  102. data/lib/little_ghost/skills/skill.rb +0 -3
  103. data/lib/little_ghost/stream_event.rb +8 -13
  104. data/lib/little_ghost/subagents/control_tool.rb +8 -0
  105. data/lib/little_ghost/subagents/manager.rb +53 -50
  106. data/lib/little_ghost/support/callbacks.rb +3 -1
  107. data/lib/little_ghost/support/content_capture.rb +3 -3
  108. data/lib/little_ghost/support/http_client.rb +2 -2
  109. data/lib/little_ghost/support/redactor.rb +1 -1
  110. data/lib/little_ghost/swarm.rb +13 -5
  111. data/lib/little_ghost/tool.rb +157 -64
  112. data/lib/little_ghost/tool_registry.rb +1 -1
  113. data/lib/little_ghost/tools/filesystem.rb +1 -1
  114. data/lib/little_ghost/tools/shell.rb +4 -3
  115. data/lib/little_ghost/tools/write_todos.rb +6 -1
  116. data/lib/little_ghost/tracing/open_telemetry.rb +1 -1
  117. data/lib/little_ghost/version.rb +1 -1
  118. data/lib/little_ghost/workflow.rb +30 -21
  119. data/lib/little_ghost/workspace.rb +222 -8
  120. data/lib/little_ghost.rb +40 -27
  121. metadata +104 -3
  122. data/lib/little_ghost/unrestricted_sandbox.rb +0 -306
@@ -1,182 +1,180 @@
1
1
  # Core Concepts
2
2
 
3
- Start with one agent. In LittleGhost, an **agent** is a reusable Ruby definition for one model loop: it selects a model, supplies instructions, exposes tools, and decides when the model has finished answering one request.
4
-
5
- ```text
6
- shared configuration
7
- └── ModelResolver ── resolves model selections ──> provider clients
8
-
9
- one request
10
- └── Run
11
- ├── CustomerSupportAgent
12
- │ ├── HelpCenterLookupTool
13
- │ └── ResearchAgent subagent (model-directed)
14
- └── sessions, resources, usage, events, and terminal result
15
- ```
16
-
17
- The sections below build outward from that unit. After the agent, the guide introduces its model, tools, and run lifecycle. It then names an **assembly**: anything a caller can invoke like one agent, including coordinated workflows, swarms, and graphs.
18
-
19
- ## Agents declare one model-driven behavior
20
-
21
- An agent class keeps the behavior for one application role together:
3
+ Define an Agent in a Ruby class, then call it with `.ask`.
22
4
 
23
5
  ```ruby
24
6
  class CustomerSupportAgent < LittleGhost::Agent
25
- description "Answers customer support questions."
26
- model :customer_support
27
- system_prompt "Answer clearly. Check the help center before stating company guidance."
7
+ model "openrouter:openai/gpt-5.6-luna"
8
+ system_prompt "Answer customer questions clearly."
28
9
  tools HelpCenterLookupTool
29
10
  end
30
- ```
31
11
 
32
- The class-level DSL is inheritable. It can declare prompts, limits, callbacks, tool classes, structured results, context management, skills, and delegation. Capabilities remain inactive until their corresponding DSL is called.
12
+ run = CustomerSupportAgent.ask("Where is my order?")
13
+ run.response
14
+ ```
33
15
 
34
- `CustomerSupportAgent.ask` creates a standalone entrypoint, consumes one `LittleGhost::Run`, and returns that run. `CustomerSupportAgent.stream_ask` creates the same entrypoint and yields events while it works. Create `CustomerSupportAgent.new(runtime:)` explicitly when several calls should reuse one runtime.
16
+ From there, add only what the work needs. Give the agent a tool. Let it ask a specialist for help. Or coordinate several agents while the rest of your application keeps making the same call.
35
17
 
36
- ```ruby
37
- run = CustomerSupportAgent.ask("Can I get a refund?")
38
- run.response # final text from the top-level execution
39
- run.result.output # text, or a validated structured value when declared
40
- ```
18
+ ## An Agent owns one model loop
41
19
 
42
- ## Models can be selected directly or by role
20
+ An **Agent** defines one model-driven behavior. It chooses the model, supplies the instructions and tools, and carries one request through to an answer.
43
21
 
44
- An agent can name a canonical target directly when the choice belongs beside its behavior:
22
+ The class holds the behavior you want to reuse. Each call brings its own input, history, context, settings, and attachments. Request data never needs to live on the class.
45
23
 
46
- ```ruby
47
- class CustomerSupportAgent < LittleGhost::Agent
48
- model "openai:gpt-5.6-luna"
49
- end
24
+ ```text
25
+ CustomerSupportAgent
26
+ ├── model selection
27
+ ├── system prompt
28
+ ├── HelpCenterLookupTool
29
+ └── limits and optional capabilities
50
30
  ```
51
31
 
52
- It can also attach trusted model settings without defining a shared profile:
32
+ An Agent can return text or checked, structured data. You can add streaming,
33
+ saved conversations, or callbacks later. None of them are required to begin.
53
34
 
54
- ```ruby
55
- class DeliberateSupportAgent < LittleGhost::Agent
56
- model(provider: "openai", model: "gpt-5.6-luna", reasoning_effort: "high")
57
- end
58
- ```
35
+ ## A Tool connects the model to Ruby
59
36
 
60
- In both forms, `provider` is the name of a configured connection. A role such as `customer_support` adds stable application vocabulary when several agents or deployments should share routing policy:
37
+ A **Tool** is one focused thing an agent can ask your application to do. It has a name, a description, an input schema, and the Ruby code that does the work.
61
38
 
62
39
  ```ruby
63
- LittleGhost.configure do |config|
64
- config.providers = {
65
- openai: {adapter: :openai, api_key: ENV.fetch("OPENAI_API_KEY")}
66
- }
67
- config.models = {
68
- customer_support: {target: "openai:gpt-5.6-luna"}
69
- }
70
- end
71
- ```
40
+ class HelpCenterLookupTool < LittleGhost::Tool
41
+ description "Look up a help center entry by topic."
42
+ input_schema(
43
+ type: "object",
44
+ properties: {topic: {type: "string"}},
45
+ required: ["topic"],
46
+ additionalProperties: false
47
+ )
72
48
 
73
- ```ruby
74
- class CustomerSupportAgent < LittleGhost::Agent
75
- model :customer_support
49
+ def call(input)
50
+ {"refunds" => "Refunds are available within 30 days."}
51
+ .fetch(input.fetch("topic"))
52
+ end
76
53
  end
77
54
  ```
78
55
 
79
- Strings and symbols without a colon are roles; strings containing a colon are canonical targets; mappings require `provider` and `model`, with remaining keys treated as model settings. Role names cannot contain a colon. Direct targets and mappings bypass role inheritance and overlays.
56
+ LittleGhost checks the model's arguments, calls the Tool, and gives the result
57
+ back to the model. The schema checks shape, not permission. Check permission
58
+ inside the Tool using identity and account information from your application.
80
59
 
81
- Dotted roles inherit from the nearest registered parent. `ResearchAgent` can request `customer_support.research` and initially use the `customer_support` profile; registering `customer_support.research` later specializes it. A resolver caller may pass an explicit `profiles:` overlay without mutating the configured profiles or agent class. Because an overlay can select a different registered provider, model, and settings, it is trusted application configuration and must be constructed or allowlisted by the application rather than copied from unchecked request data. The base resolver does not inspect application-specific invocation fields.
60
+ [Tools](tools.md) follows that path from model input to application code,
61
+ including run-scoped bindings, concurrency, retries, and sandbox delegation.
82
62
 
83
- The provider performs model I/O. `LittleGhost::ModelResolver` resolves application intent into a `LittleGhost::Model`, which carries the provider, target, settings, details, and role for a run.
63
+ ## A Run owns one top-level execution
84
64
 
85
- ## Runs own top-level lifecycle
65
+ Every `.ask` or `.stream_ask` creates a **Run**. Think of it as the record of one trip through LittleGhost. It opens what the request needs, records how the work ended, and closes the resources it owns.
86
66
 
87
- A `LittleGhost::Run` owns one top-level execution. It opens the session, workspace, sandbox, agent entrypoint, and registered resources, then closes owned resources in reverse order. Later sections show how the same lifecycle can own a coordinated entrypoint.
67
+ ```ruby
68
+ run = CustomerSupportAgent.ask("Where is order 481?")
88
69
 
89
- The run is both executable and enumerable. `#call` consumes it; `#each` streams `LittleGhost::StreamEvent` objects. After termination, the run reports one outcome: completed, failed, partial at a deadline, or cancelled. It also exposes the final response, result, usage, and error.
70
+ run.completed? # => true
71
+ run.response
72
+ # One possible response: Order 481 is out for delivery.
73
+ run.usage # => normalized token usage
74
+ run.result # => the complete LittleGhost::RunResult
75
+ ```
90
76
 
91
- Long-lived services can supervise a run without making their request thread own its execution:
77
+ The Agent defines reusable behavior; the Run records what happened this time.
92
78
 
93
- ```ruby
94
- execution = CustomerSupportAgent.new.start_execution(
95
- message: "Investigate transfer 481"
96
- ) do |event|
97
- event_buffer << event
98
- end
79
+ ### Follow one request
80
+
81
+ One Run owns the trip from request to result:
99
82
 
100
- execution.interrupt_response(message: "Include the latest ledger entry")
101
- run = execution.wait(deadline: Time.now + 30)
83
+ ```text
84
+ Run
85
+ ├── Invocation: caller input, history, and application context
86
+ ├── RunContext: mutable working state for this execution
87
+ └── Agent and Tools ──> RunResult
102
88
  ```
103
89
 
104
- `LittleGhost::Execution` owns the worker, preserves request-scoped execution state, and coordinates cancellation, interruptions, waiting, and bounded shutdown. The underlying run still owns agent resources and its terminal outcome. Event consumers run on the worker thread. Applications should keep them thread-safe and avoid blocking indefinitely.
90
+ An **Invocation** is the request in LittleGhost's standard shape. Its `context`
91
+ contains current request values supplied by your application. A Tool can read
92
+ those values through `run.invocation.context` when it checks permission.
105
93
 
106
- An `Invocation` is the request envelope. It normalizes the current message and history, generates missing identifiers, and retains application-specific fields with indifferent string and symbol keys. Caller identity remains explicit. If session persistence needs tenant isolation, derive its actor from trusted authentication state; never trust a model-supplied or unverified request field.
94
+ A **Session** stores conversation state between Runs when persistence is
95
+ configured. The **RunContext** carries mutable working state in `context.state`
96
+ during one Run. LittleGhost loads saved Session state before adding the current
97
+ Invocation context. Recheck saved values before using them for permission
98
+ decisions.
107
99
 
108
- ## Tools are validated application boundaries
100
+ A Tool's **Binding** gives the Tool access to objects created for this run,
101
+ including the Agent, Run, Workspace, and Sandbox. These objects are separate
102
+ from arguments chosen by the model. [Tools](tools.md) explains the binding;
103
+ [Workspaces and Sandboxes](sandboxing.md) explains delegated files and child
104
+ processes.
109
105
 
110
- `HelpCenterLookupTool` exposes exactly one operation to the model:
106
+ The final **RunResult** keeps the complete assembly result. Its `text` is the final text answer. Its `output` returns structured data when the Agent declared a result schema, and text otherwise. The top-level `Run#response` is always the caller-facing text.
111
107
 
112
- ```ruby
113
- class HelpCenterLookupTool < LittleGhost::Tool
114
- description "Look up a help center entry by topic."
115
- input_schema(
116
- type: "object",
117
- properties: {topic: {type: "string"}},
118
- required: ["topic"],
119
- additionalProperties: false
120
- )
121
-
122
- def call(input)
123
- HelpCenterRepository.fetch(input.fetch("topic"))
124
- end
125
- end
126
- ```
108
+ ### See how a call ended
127
109
 
128
- LittleGhost validates the model's input before invoking `#call`. Hashes and arrays returned by a tool are JSON-encoded; other values become text. Expected application failures can raise `LittleGhost::ToolError`; unexpected exception messages are sanitized before they reach model context.
110
+ Top-level calls normally return a Run, even when execution fails. The terminal event carries the same outcome when you stream:
129
111
 
130
- A tool can return a `LittleGhost::Tool::ExecutionResult` with `companion_content` when the next model request also needs text, images, or documents. LittleGhost keeps the ordinary tool result intact, then appends each tool's companion blocks as a transient user message in tool-call order. Session persistence omits those transient messages. Tool-use, tool-result, and reasoning blocks are rejected as companion content.
112
+ | What happened | Run outcome | Terminal event | What Ruby does |
113
+ | --- | --- | --- | --- |
114
+ | The assembly completed | `completed` | `:run_stop` | Returns the Run |
115
+ | Model, provider, or assembly execution failed | `failed` | `:run_error` | Returns the Run; inspect `run.error` |
116
+ | The deadline stopped work | `partial` | `:run_partial` | Returns the Run with any response produced so far |
117
+ | Cancellation stopped work | `cancelled` | `:run_cancel` | Returns the Run without a response |
118
+ | Tool input or a `ToolError` failed | The model may recover | No terminal event by itself | Gives a safe error result back to the model |
119
+ | Input, configuration, or resources failed before a Run could start | No Run exists | None | Raises the exception |
131
120
 
132
- Validation is not authorization. A tool that reads customer records, writes files, executes processes, or calls a network service must enforce the application's trust rules itself. The built-in unrestricted sandbox executes with the Ruby process's permissions and is not a security boundary. Configure an isolated sandbox before exposing filesystem or shell tools to untrusted work.
121
+ Unexpected Tool exception messages are hidden from the model. The original
122
+ exception remains available to application callbacks and diagnostics.
133
123
 
134
- ## Assemblies let coordination look like one agent
124
+ Failures while closing resources, delivering events, or reporting
125
+ instrumentation sit outside the normal result path. They raise a Ruby exception
126
+ because LittleGhost can no longer promise that it delivered a clean ending.
127
+ [Running in Production](production.md) covers supervision and shutdown.
135
128
 
136
- An **assembly** is any LittleGhost entrypoint that a caller can use like one agent. `CustomerSupportAgent` is therefore the smallest assembly: it contains one agent and one model loop.
129
+ ## An Assembly can look like one Agent
137
130
 
138
- When a feature needs several agents, three coordination classes preserve that same caller interface:
131
+ One model loop is not always enough. LittleGhost calls any unit that a caller can invoke like an Agent an **Assembly**.
139
132
 
140
- - A `Workflow` uses Ruby code to enforce ordering, branching, and parallel work.
141
- - A `Swarm` lets configured agents choose direct handoffs to one another.
142
- - A `Graph` follows named nodes and application-declared edges.
133
+ An Agent is the smallest Assembly. Workflow, Swarm, and Graph coordinate several participants while preserving the same entrypoints:
143
134
 
144
135
  ```ruby
145
- CustomerSupportAgent.ask("Can I get a refund?")
146
- ResponseWorkflow.ask("Can I get a refund?")
147
- ProblemSolverSwarm.ask("Can I get a refund?")
148
- SupportFlowGraph.ask("Can I get a refund?")
136
+ CustomerSupportAgent.ask(question)
137
+ ResponseWorkflow.ask(question)
138
+ ProblemSolverSwarm.ask(question)
139
+ SupportFlowGraph.ask(question)
149
140
  ```
150
141
 
151
- Each call returns a top-level `LittleGhost::Run`, and each `stream_ask` yields the same event vocabulary. The caller chooses an entrypoint without needing to branch on its internal coordination style. Instances also share `call`, `stream`, `start_execution`, interruption, and `as_tool` behavior.
142
+ That shared calling style is what makes composition feel natural. A controller, job, or CLI does not need to know whether one Agent answered or a whole support process worked together.
152
143
 
153
- Keep agent definitions in `app/agents`. Put workflow, swarm, and graph definitions in `app/assemblies`, with class names ending in `Workflow`, `Swarm`, or `Graph`. The next sections explain when each form earns its name.
144
+ ## Choose who controls the next step
154
145
 
155
- ## Subagents are model-directed delegation
146
+ The coordination types differ mainly in who decides what happens next:
156
147
 
157
- Declaring `ResearchAgent` as a subagent gives `CustomerSupportAgent` a configured set of tools for spawning, messaging, interrupting, waiting for, and listing research work:
148
+ | Need | Choose | Who controls the next step? |
149
+ | --- | --- | --- |
150
+ | One model-driven behavior | Agent | The active model loop |
151
+ | A model should delegate a named task | Subagent | The parent model |
152
+ | Ruby should enforce ordering or branching | Workflow | The workflow's Ruby code |
153
+ | Specialists should choose permitted handoffs | Swarm | The active agent |
154
+ | Allowed routes should be visible in advance | Graph | Declared nodes and edges |
158
155
 
159
- ```ruby
160
- class ResearchAgent < LittleGhost::Agent
161
- description "Investigates support questions that need broader research."
162
- model "customer_support.research"
163
- system_prompt "Return a concise evidence summary."
164
- end
156
+ ### Subagents bring in a specialist
165
157
 
158
+ A **subagent** is a specialist that a parent Agent can call for help. The parent model chooses when to delegate, reads the result, and then continues its own answer.
159
+
160
+ ```ruby
166
161
  class CustomerSupportAgent < LittleGhost::Agent
167
- model "customer_support"
168
- tools HelpCenterLookupTool
162
+ model "openrouter:openai/gpt-5.6-luna"
169
163
  subagent ResearchAgent, kind: "research"
170
164
  end
171
165
  ```
172
166
 
173
- The model decides whether to delegate and how to use the returned research. Each child declares its own tools, so access remains visible at the class receiving it. Subagent work can run concurrently and respects the configured turn, concurrency, depth, and time limits. Conversations can persist when a session store exists; `persist: false` keeps a declaration invocation-local.
167
+ Use a subagent when delegation is part of one model's decision-making. Use a Workflow when application code must guarantee that a step happens.
168
+
169
+ When an Agent also uses code mode, subagent controls stay in the Agent's
170
+ conversation. Code-mode programs can compose ordinary Tools, while spawning,
171
+ messaging, and checking on subagents remain decisions for the parent model.
174
172
 
175
- Use an agent as an ordinary tool with `agent_as_tool` when one request and one result is enough. Use a subagent when the parent needs an addressable worker with follow-ups, progress, interruption, or durable conversation identity.
173
+ ### Workflows make Ruby the coordinator
176
174
 
177
- ## Workflows are application-directed composition
175
+ A **Workflow** coordinates work with ordinary Ruby. Its `perform` method can call an Agent or another Assembly, read a result, choose a branch, or run independent steps together.
178
176
 
179
- Some customer support requests must always be researched before a response is written. Put that invariant in Ruby rather than asking the model to remember it:
177
+ `invoke` prepares a lazy child call. Reading `.output` runs an intermediate child. Return the final `invoke` itself, without reading its output, so that answer can stream to the caller.
180
178
 
181
179
  ```ruby
182
180
  class ResponseWorkflow < LittleGhost::Workflow
@@ -184,9 +182,7 @@ class ResponseWorkflow < LittleGhost::Workflow
184
182
 
185
183
  def perform
186
184
  research = invoke(ResearchAgent).output
187
-
188
185
  invoke CustomerSupportAgent, input: <<~PROMPT
189
- Customer request:
190
186
  #{input.text}
191
187
 
192
188
  Research:
@@ -196,157 +192,72 @@ class ResponseWorkflow < LittleGhost::Workflow
196
192
  end
197
193
  ```
198
194
 
199
- `#invoke` builds a lazy Assembly invocation, so a workflow step may be an agent, workflow, swarm, or graph. Calling `#output` consumes an intermediate invocation; `#perform` must return its final invocation unconsumed so LittleGhost can stream it to the original caller. Input, history, state, settings, cancellation, deadline, template values, and trace parentage flow through the workflow, while intermediate usage is added to the terminal result.
200
-
201
- Unlike Graph nodes and Swarm members, each Workflow invocation receives the full caller history and application context by default. Isolated copies prevent one child from mutating a sibling's context; they do not prevent disclosure. Pass `history: []`, `context: {}`, or explicitly redacted values to `invoke` when participants use different providers or privileges.
202
-
203
- Independent invocations can run concurrently while ordinary Ruby still controls composition:
204
-
205
- ```ruby
206
- research, verification = parallel(
207
- invoke(ResearchGraph, as: :research),
208
- invoke(VerificationWorkflow, as: :verification),
209
- max_concurrency: 2
210
- )
211
- ```
212
-
213
- Results preserve declaration order. Each branch receives isolated application context and cooperative cancellation. `timeout:`, `retries:`, `retry_on:`, and `retry_delay:` apply to `invoke`; retries require explicit exception classes because rerunning an Assembly may repeat tool side effects.
195
+ Workflow children receive the caller's history and application context by default. Pass `history: []`, `context: {}`, or redacted values when a participant should receive less.
214
196
 
215
- Cancellation, deadlines, and step timeouts are cooperative. They do not forcibly stop provider or tool code, and they do not roll back external side effects. A participant must honor its cancellation token or deadline, and applications must decide whether an operation is safe to retry.
197
+ ### Swarms let agents hand work to one another
216
198
 
217
- A workflow has the same entrypoint API as an agent:
218
-
219
- ```ruby
220
- run = ResponseWorkflow.ask("Review this unusual refund request")
221
-
222
- puts run.response
223
- ```
224
-
225
- Choose a subagent when delegation is part of the model's judgment. Choose a workflow when ordering and branching are application invariants. They can coexist: `ResponseWorkflow` can always collect baseline research, while `CustomerSupportAgent` can still delegate a new question that arises while drafting the response.
226
-
227
- ## Swarms use direct agent handoffs
228
-
229
- A swarm keeps one member active at a time and injects one reserved `handoff_to_agent` tool. A member either answers the caller or hands the request directly to another configured member:
199
+ A **Swarm** is a group of Agents that can hand work to one another. One member is active at a time. It can answer the caller or choose one of its allowed specialists.
230
200
 
231
201
  ```ruby
232
202
  class ProblemSolverSwarm < LittleGhost::Swarm
233
203
  member TriageAgent
234
204
  member BillingAgent
235
205
  member AccountAgent
206
+
236
207
  start TriageAgent
237
208
  handoff TriageAgent, to: [BillingAgent, AccountAgent]
238
- max_steps 12
239
- max_handoff_repeats 3
240
209
  end
241
210
  ```
242
211
 
243
- Members are fresh Agent instances; unlike Workflow invocations and Graph nodes, Swarm members intentionally remain Agent-only so handoffs stay direct and local. A complete Swarm can still be used as a Workflow step, Graph node, or tool. A handoff names the next member and supplies a message plus optional JSON-like context. That context remains untrusted model-authored prompt content; it does not become trusted application state. A member cannot hand off to itself, hand off outside the allowed topology, or combine a handoff with another tool call. Without `handoff` declarations, routing remains all-to-all except self-handoffs. A member with no declared outgoing target receives no handoff tool. Invalid calls return an ordinary tool error so the model can recover. If no handoff occurs, the current member's response is final. Members receive only the current request or explicit handoff envelope by default; opt into original caller data with `history: true` or `context: true` on that member.
244
-
245
- Potentially intermediate model text is omitted from the caller's ordinary response stream. Streams expose Assembly lifecycle events, then the final member's ordinary response events. `max_steps` bounds total work and `max_handoff_repeats` detects repeated directed transitions; either limit raises `AssemblyLimitError` when exhausted. Members also accept the shared cooperative retry and timeout options described for workflows.
212
+ A Swarm is intentionally agent-to-agent. Its members are Agents, not other
213
+ kinds of Assembly. Caller history and application context stay hidden unless a
214
+ member opts in. A handoff message comes from another model, so a receiving
215
+ Agent should use it as context rather than proof that an action is permitted.
246
216
 
247
- ## Graphs guide serial and parallel paths
217
+ ### Graphs make routes visible
248
218
 
249
- A graph names Assembly nodes and directed edges. Ordinary edges select exactly one next node, while explicit forks and joins add bounded parallel work without shared mutable reducers:
219
+ A **Graph** connects named Assembly nodes with declared edges. Nodes can contain Agents, Workflows, Swarms, or other Graphs.
250
220
 
251
221
  ```ruby
252
222
  class SupportFlowGraph < LittleGhost::Graph
253
223
  node :triage, TriageAgent
254
- node :research, ResearchAgent
255
- node :verify, VerificationWorkflow
224
+ node :billing, BillingAgent
225
+ node :general, CustomerSupportAgent
256
226
  node :respond, CustomerSupportAgent
257
227
 
258
228
  start :triage
259
- fork :triage, to: [:research, :verify], max_concurrency: 2
260
- join [:research, :verify], to: :respond
229
+ edge :triage, :billing do |state|
230
+ state.result(:triage).output == "billing"
231
+ end
232
+ edge :triage, :general
233
+ edge :billing, :respond
234
+ edge :general, :respond
261
235
  finish :respond
262
- max_steps 12
263
236
  end
264
237
  ```
265
238
 
266
- An edge condition receives immutable `Graph::State`, including the original input, history, context, step, current and previous node names, predecessors, branch results, completed results, and a routed error when present. Exactly one matching conditional edge wins; otherwise one unconditional fallback is used. An `error_edge` can route selected application errors after retries are exhausted. Cancellation, parent deadlines, and cleanup failures always remain control flow.
267
-
268
- By default, a downstream node receives the original multimodal input plus labeled predecessor output. A join receives every branch output in declaration order. Pass `input: ->(state) { ... }` on an edge, error edge, or join to replace that mapping. Nodes receive no caller history or application context unless their declaration opts in with `history: true` or `context: true`. The original request and routed outputs still cross node and provider boundaries by default, and routing callbacks can inspect the original context through `Graph::State`; map or redact inputs explicitly when participants have different privileges. Nodes may name any Assembly type, class, builder, or immutable definition. Fork branches may follow ordinary edges before reaching their distinct declared join sources.
269
-
270
- `validate!` catches invalid topology before model work begins, and `to_mermaid` renders a deterministic diagram. A graph suppresses intermediate ordinary model stream events, publishes lifecycle, transition, fork, join, retry, and error events, aggregates usage, and forwards only the finish node's ordinary response stream. Downstream nodes still receive routed outputs, and terminal step records retain bounded semantic outputs for callers to inspect.
271
-
272
- Composite `RunResult` objects expose immutable `steps` and a `trajectory`. Step records include participants, attempts, timing, usage, relationships, and bounded semantic outputs without retaining transcripts or tool payloads. Swarm handoffs retain only their explicit handoff envelope. These records support assertions such as `result.trajectory.concurrent?(first_id, second_id)` without coupling tests to a tracing backend.
273
-
274
- ## Builders unlock definitions discovered at runtime
275
-
276
- Class definitions are the default because they keep behavior, names, and topology close together. Each agent or assembly class can produce an immutable `.definition` snapshot or an independent mutable `.to_builder` variant.
277
-
278
- Use a builder when application code discovers participants or routes at runtime. The builder records the same declaration that the class DSL would organize:
279
-
280
- ```ruby
281
- graph = LittleGhost::GraphBuilder.new(id: "support_flow")
282
- graph.node :triage, TriageAgent
283
- graph.node :respond, CustomerSupportAgent
284
- graph.start :triage
285
- graph.edge :triage, :respond
286
- graph.finish :respond
287
- graph.validate!
288
-
289
- run = graph.ask("Can I get a refund?")
290
- ```
291
-
292
- `AgentBuilder`, `WorkflowBuilder`, `SwarmBuilder`, and `GraphBuilder` share the Assembly execution API. Builders remain mutable; each build or invocation recursively snapshots declaration containers and referenced Assembly definitions, so later builder declarations affect only future executions. Definitions are Ruby objects rather than portable JSON because conditions, callbacks, workflow bodies, factories, and resolvers may contain executable Ruby. Those closures and their external dependencies remain live trusted application code; the snapshot does not freeze state they capture.
239
+ Graph nodes receive the original task and results from the nodes immediately
240
+ before them. They do not receive caller history or application context unless
241
+ their declarations opt in. [Compose Agents](assemblies.md) explains parallel
242
+ routes, joins, input mapping, and data boundaries.
293
243
 
294
- ## Assemblies can be tools
244
+ ## One result, even when several agents help
295
245
 
296
- Any assembly instance supports `as_tool`. Agent classes can also declare another assembly as a tool:
246
+ Every assembly produces the same top-level `Run` and final `RunResult`. Composite assemblies also keep a size-limited record of the participants that ran:
297
247
 
298
248
  ```ruby
299
- class CustomerSupportAgent < LittleGhost::Agent
300
- assembly_as_tool SupportFlowGraph, name: "investigate_support_case"
301
- end
302
- ```
303
-
304
- `assemblies_as_tools` declares several with shared options. Existing `agent_as_tool` and `agents_as_tools` remain agent-specific aliases. Composite assemblies do not accept agent-only model or tool overrides.
305
-
306
- An assembly tool receives the invoking tool context's application state on every call. `preserve_context: false` prevents conversational history from carrying between calls; it does not suppress that application state. Nested tools must continue to authorize privileged work from trusted context rather than from model-authored input.
249
+ run = SupportFlowGraph.ask("Why was I charged twice?")
307
250
 
308
- ## Structured results separate data from prose
309
-
310
- An agent that feeds application code can declare a strict JSON object schema:
311
-
312
- ```ruby
313
- class ResearchAgent < LittleGhost::Agent
314
- model "customer_support.research"
315
- result_schema(
316
- {
317
- type: "object",
318
- properties: {
319
- summary: {type: "string"},
320
- sources: {type: "array", items: {type: "string"}}
321
- },
322
- required: %w[summary sources],
323
- additionalProperties: false
324
- },
325
- name: "support_research"
326
- )
327
- end
251
+ run.response
252
+ run.result.steps
253
+ run.result.trajectory.transitions
328
254
  ```
329
255
 
330
- LittleGhost selects provider-native structured output when the resolved model advertises it, or a strict terminal tool when supported. The locally validated value is available through `RunResult#structured_result` and `RunResult#output`. Invalid output receives one repair attempt, then raises `LittleGhost::StructuredResultError`.
331
-
332
- Use structured results when code consumes fields. Keep ordinary text when a human is the final consumer.
333
-
334
- ## Sessions preserve conversation, streams expose progress
335
-
336
- The default session store is in-memory. A configured `SessionStore` can load history and state before an agent runs and checkpoint coherent turns as work progresses. The application must supply stable session and actor identifiers when it wants continuity and isolation.
337
-
338
- Applications that need to reconcile persisted messages with invocation history can register a `LittleGhost::Runtime::Hook` and implement `session_history`. The hook receives the run plus `stored:` and `fallback:` message collections. Return the history to use, or `nil` to defer to the next hook and ultimately the session default. This keeps application-specific reconciliation policy outside the framework session type.
339
-
340
- Streams expose generic framework events rather than provider wire formats. Consumers can render text deltas, observe tool or subagent activity, collect usage, and react to terminal outcomes without coupling to OpenAI, OpenRouter, or Bedrock. The optional AG-UI adapter translates the same events at an interface boundary.
341
-
342
- ## Keep the boundary visible
343
-
344
- The core design can be summarized as five choices:
256
+ This record shows which participants ran. [Compose Agents](assemblies.md)
257
+ explains builders, detailed routing records, and live events from nested Agents.
345
258
 
346
- - Put shared construction and provider policy in configuration; use inline declarations or independent YAML files according to the application's needs.
347
- - Put model behavior and available capabilities on agent classes.
348
- - Put privileged application operations behind narrow, authorized tools.
349
- - Put imperative ordering in workflows, dynamic peer routing in swarms, and guided routing in graphs.
350
- - Leave addressable background delegation to subagents.
259
+ The pieces now fit together: Agents define behavior. Tools connect them to Ruby. Runs record one execution. Assemblies let the system grow without changing the caller.
351
260
 
352
- Return to [Getting Started](getting_started.md) for the complete first-run setup. The API reference covers exact signatures and lifecycle details for `LittleGhost::Runtime`, `LittleGhost::Run`, `LittleGhost::Execution`, `LittleGhost::Assembly`, `LittleGhost::Agent`, `LittleGhost::Tool`, `LittleGhost::Workflow`, `LittleGhost::Swarm`, `LittleGhost::Graph`, and `LittleGhost::ModelResolver`.
261
+ Continue with [Models and Providers](models_and_providers.md) to choose model
262
+ targets and configure provider connections. When you need several agents to
263
+ work together, [Compose Agents](assemblies.md) builds on the same concepts.