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.
- checksums.yaml +4 -4
- data/README.md +90 -84
- data/docs/guides/assemblies.md +412 -0
- data/docs/guides/code_mode.md +275 -0
- data/docs/guides/core_concepts.md +150 -239
- data/docs/guides/getting_started.md +125 -87
- data/docs/guides/integrations.md +217 -0
- data/docs/guides/models_and_providers.md +125 -0
- data/docs/guides/production.md +253 -0
- data/docs/guides/prompt_views.md +139 -0
- data/docs/guides/sandboxing.md +282 -0
- data/docs/guides/skills.md +141 -0
- data/docs/guides/structured_outputs_and_content.md +135 -0
- data/docs/guides/tools.md +329 -0
- data/lib/little_ghost/ag_ui/adapter.rb +5 -5
- data/lib/little_ghost/agent/delegation.rb +3 -3
- data/lib/little_ghost/agent/skills.rb +6 -1
- data/lib/little_ghost/agent/tool_loop.rb +6 -2
- data/lib/little_ghost/agent.rb +411 -214
- data/lib/little_ghost/agent_builder.rb +61 -22
- data/lib/little_ghost/{agent_interruptions.rb → agent_interjections.rb} +12 -12
- data/lib/little_ghost/agent_stream_source.rb +262 -0
- data/lib/little_ghost/artifact.rb +182 -0
- data/lib/little_ghost/artifacts/presentation_budget.rb +44 -0
- data/lib/little_ghost/artifacts/workspace_store.rb +370 -0
- data/lib/little_ghost/artifacts.rb +11 -0
- data/lib/little_ghost/assembly.rb +110 -30
- data/lib/little_ghost/assembly_builder.rb +59 -21
- data/lib/little_ghost/assembly_execution.rb +110 -6
- data/lib/little_ghost/code_mode/broker.rb +164 -0
- data/lib/little_ghost/code_mode/catalog.rb +54 -0
- data/lib/little_ghost/code_mode/engine.rb +58 -0
- data/lib/little_ghost/code_mode/javascript/catalog.rb +118 -0
- data/lib/little_ghost/code_mode/javascript/client.rb +550 -0
- data/lib/little_ghost/code_mode/javascript/host.rb +347 -0
- data/lib/little_ghost/code_mode/javascript/session.rb +579 -0
- data/lib/little_ghost/code_mode/javascript_engine.rb +172 -0
- data/lib/little_ghost/code_mode/protocol.rb +78 -0
- data/lib/little_ghost/code_mode/ruby/catalog.rb +80 -0
- data/lib/little_ghost/code_mode/ruby/host.rb +102 -0
- data/lib/little_ghost/code_mode/ruby/session.rb +508 -0
- data/lib/little_ghost/code_mode/ruby_engine.rb +86 -0
- data/lib/little_ghost/code_mode/runtime.rb +335 -0
- data/lib/little_ghost/code_mode/session.rb +61 -0
- data/lib/little_ghost/code_mode/types.rb +58 -0
- data/lib/little_ghost/code_mode.rb +53 -0
- data/lib/little_ghost/configuration.rb +366 -47
- data/lib/little_ghost/content.rb +24 -13
- data/lib/little_ghost/data_map.rb +209 -0
- data/lib/little_ghost/errors.rb +28 -9
- data/lib/little_ghost/execution.rb +33 -34
- data/lib/little_ghost/graph.rb +376 -197
- data/lib/little_ghost/mcp/client.rb +487 -88
- data/lib/little_ghost/mcp/toolset.rb +210 -0
- data/lib/little_ghost/mcp/types.rb +216 -0
- data/lib/little_ghost/mcp.rb +3 -0
- data/lib/little_ghost/message.rb +4 -4
- data/lib/little_ghost/model_capabilities.rb +9 -6
- data/lib/little_ghost/model_request.rb +0 -12
- data/lib/little_ghost/model_resolver.rb +15 -5
- data/lib/little_ghost/model_response.rb +3 -7
- data/lib/little_ghost/network/authorizer_server.rb +162 -0
- data/lib/little_ghost/network/certificate_authority.rb +98 -0
- data/lib/little_ghost/network/envoy_config.rb +362 -0
- data/lib/little_ghost/network/envoy_gateway.rb +409 -0
- data/lib/little_ghost/network/external_gateway.rb +68 -0
- data/lib/little_ghost/network.rb +96 -0
- data/lib/little_ghost/prompt_resolver.rb +9 -7
- data/lib/little_ghost/provider_registry.rb +3 -3
- data/lib/little_ghost/providers/anthropic.rb +8 -1
- data/lib/little_ghost/providers/gemini.rb +10 -1
- data/lib/little_ghost/providers/vertex_ai.rb +6 -1
- data/lib/little_ghost/run.rb +162 -54
- data/lib/little_ghost/run_context.rb +42 -20
- data/lib/little_ghost/run_result.rb +0 -7
- data/lib/little_ghost/runtime/hook.rb +8 -3
- data/lib/little_ghost/runtime/hooks/artifacts.rb +338 -0
- data/lib/little_ghost/runtime.rb +160 -52
- data/lib/little_ghost/sandbox/capabilities.rb +68 -0
- data/lib/little_ghost/sandbox/environment_policy.rb +41 -0
- data/lib/little_ghost/sandbox/filesystem.rb +377 -0
- data/lib/little_ghost/sandbox/isolated_backend.rb +163 -0
- data/lib/little_ghost/sandbox/limits.rb +48 -0
- data/lib/little_ghost/sandbox/mount.rb +129 -0
- data/lib/little_ghost/sandbox/network_policy.rb +108 -0
- data/lib/little_ghost/sandbox/policy.rb +88 -0
- data/lib/little_ghost/sandbox/process_runner.rb +103 -0
- data/lib/little_ghost/sandbox/process_session.rb +335 -0
- data/lib/little_ghost/sandbox/scope.rb +304 -0
- data/lib/little_ghost/sandbox.rb +158 -32
- data/lib/little_ghost/sandboxes/bubblewrap.rb +351 -0
- data/lib/little_ghost/sandboxes/native.rb +51 -0
- data/lib/little_ghost/sandboxes/seatbelt.rb +261 -0
- data/lib/little_ghost/sandboxes/unrestricted.rb +241 -0
- data/lib/little_ghost/session.rb +39 -26
- data/lib/little_ghost/session_store.rb +9 -5
- data/lib/little_ghost/session_stores/agent_core_memory.rb +64 -56
- data/lib/little_ghost/session_stores/filesystem.rb +261 -0
- data/lib/little_ghost/session_stores/memory.rb +7 -0
- data/lib/little_ghost/skills/catalog.rb +94 -14
- data/lib/little_ghost/skills/resource_root.rb +42 -0
- data/lib/little_ghost/skills/skill.rb +0 -3
- data/lib/little_ghost/stream_event.rb +8 -13
- data/lib/little_ghost/subagents/control_tool.rb +8 -0
- data/lib/little_ghost/subagents/manager.rb +53 -50
- data/lib/little_ghost/support/callbacks.rb +3 -1
- data/lib/little_ghost/support/content_capture.rb +3 -3
- data/lib/little_ghost/support/http_client.rb +2 -2
- data/lib/little_ghost/support/redactor.rb +1 -1
- data/lib/little_ghost/swarm.rb +13 -5
- data/lib/little_ghost/tool.rb +157 -64
- data/lib/little_ghost/tool_registry.rb +1 -1
- data/lib/little_ghost/tools/filesystem.rb +1 -1
- data/lib/little_ghost/tools/shell.rb +4 -3
- data/lib/little_ghost/tools/write_todos.rb +6 -1
- data/lib/little_ghost/tracing/open_telemetry.rb +1 -1
- data/lib/little_ghost/version.rb +1 -1
- data/lib/little_ghost/workflow.rb +30 -21
- data/lib/little_ghost/workspace.rb +222 -8
- data/lib/little_ghost.rb +40 -27
- metadata +104 -3
- data/lib/little_ghost/unrestricted_sandbox.rb +0 -306
|
@@ -1,182 +1,180 @@
|
|
|
1
1
|
# Core Concepts
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
26
|
-
|
|
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
|
-
|
|
12
|
+
run = CustomerSupportAgent.ask("Where is my order?")
|
|
13
|
+
run.response
|
|
14
|
+
```
|
|
33
15
|
|
|
34
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
24
|
+
```text
|
|
25
|
+
CustomerSupportAgent
|
|
26
|
+
├── model selection
|
|
27
|
+
├── system prompt
|
|
28
|
+
├── HelpCenterLookupTool
|
|
29
|
+
└── limits and optional capabilities
|
|
50
30
|
```
|
|
51
31
|
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
63
|
+
## A Run owns one top-level execution
|
|
84
64
|
|
|
85
|
-
|
|
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
|
-
|
|
67
|
+
```ruby
|
|
68
|
+
run = CustomerSupportAgent.ask("Where is order 481?")
|
|
88
69
|
|
|
89
|
-
|
|
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
|
-
|
|
77
|
+
The Agent defines reusable behavior; the Run records what happened this time.
|
|
92
78
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
101
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
121
|
+
Unexpected Tool exception messages are hidden from the model. The original
|
|
122
|
+
exception remains available to application callbacks and diagnostics.
|
|
133
123
|
|
|
134
|
-
|
|
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
|
|
129
|
+
## An Assembly can look like one Agent
|
|
137
130
|
|
|
138
|
-
|
|
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
|
-
|
|
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(
|
|
146
|
-
ResponseWorkflow.ask(
|
|
147
|
-
ProblemSolverSwarm.ask(
|
|
148
|
-
SupportFlowGraph.ask(
|
|
136
|
+
CustomerSupportAgent.ask(question)
|
|
137
|
+
ResponseWorkflow.ask(question)
|
|
138
|
+
ProblemSolverSwarm.ask(question)
|
|
139
|
+
SupportFlowGraph.ask(question)
|
|
149
140
|
```
|
|
150
141
|
|
|
151
|
-
|
|
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
|
-
|
|
144
|
+
## Choose who controls the next step
|
|
154
145
|
|
|
155
|
-
|
|
146
|
+
The coordination types differ mainly in who decides what happens next:
|
|
156
147
|
|
|
157
|
-
|
|
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
|
-
|
|
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 "
|
|
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
|
-
|
|
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
|
-
|
|
173
|
+
### Workflows make Ruby the coordinator
|
|
176
174
|
|
|
177
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
197
|
+
### Swarms let agents hand work to one another
|
|
216
198
|
|
|
217
|
-
A
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
217
|
+
### Graphs make routes visible
|
|
248
218
|
|
|
249
|
-
A
|
|
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 :
|
|
255
|
-
node :
|
|
224
|
+
node :billing, BillingAgent
|
|
225
|
+
node :general, CustomerSupportAgent
|
|
256
226
|
node :respond, CustomerSupportAgent
|
|
257
227
|
|
|
258
228
|
start :triage
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
##
|
|
244
|
+
## One result, even when several agents help
|
|
295
245
|
|
|
296
|
-
|
|
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
|
-
|
|
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
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|