little_ghost 0.2.1 → 0.3.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 +19 -5
- data/docs/guides/core_concepts.md +167 -38
- data/docs/guides/getting_started.md +8 -4
- data/lib/little_ghost/agent/delegation.rb +34 -7
- data/lib/little_ghost/agent/tool_loop.rb +2 -1
- data/lib/little_ghost/agent.rb +115 -156
- data/lib/little_ghost/agent_builder.rb +20 -4
- data/lib/little_ghost/agent_factory.rb +3 -0
- data/lib/little_ghost/assembly.rb +311 -0
- data/lib/little_ghost/assembly_builder.rb +459 -0
- data/lib/little_ghost/assembly_execution.rb +452 -0
- data/lib/little_ghost/errors.rb +8 -0
- data/lib/little_ghost/execution.rb +206 -0
- data/lib/little_ghost/graph.rb +911 -0
- data/lib/little_ghost/run.rb +120 -32
- data/lib/little_ghost/run_result.rb +22 -11
- data/lib/little_ghost/runtime/hook.rb +7 -2
- data/lib/little_ghost/runtime.rb +64 -6
- data/lib/little_ghost/sandbox.rb +1 -1
- data/lib/little_ghost/support/executor.rb +14 -2
- data/lib/little_ghost/support/loader.rb +2 -2
- data/lib/little_ghost/support.rb +15 -3
- data/lib/little_ghost/swarm.rb +431 -0
- data/lib/little_ghost/tool.rb +32 -6
- data/lib/little_ghost/tracing/open_telemetry.rb +13 -2
- data/lib/little_ghost/unrestricted_sandbox.rb +1 -1
- data/lib/little_ghost/version.rb +1 -1
- data/lib/little_ghost/workflow.rb +198 -73
- data/lib/little_ghost.rb +11 -4
- metadata +11 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9890ec252b435a45ce78f82a57b107fbfa7f1846f4cf580e0538ce3303c2d7c5
|
|
4
|
+
data.tar.gz: 47c8aa6beeaf56a8987088007fa67e56144394a3fff1dcdb8e357812725ab7e6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 92540329eaa105782f108dc52cf0e21351b7ae60c674d6b6d786a460b221e54f569ae7f3240bdb0d8a436ea6b0102ef1d14251fa543d2d9c7522aad6d9142f4a
|
|
7
|
+
data.tar.gz: 3dcb8b76e48448cbbd8088cfc0ee5ef0edcfcdb4b595c93ed741d60ee9f852a4ece504794119bdd874761ed1e40af97f7e05f6bb3f81884c9b8d4c4b0656a43a
|
data/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
<hr>
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Build an agent inside an existing Ruby system, or use LittleGhost as the core of a dedicated AI service. An agent is a reusable Ruby class that gives one model a prompt, tools, limits, and a model selection. LittleGhost connects that class to providers, streaming, sessions, delegation, and observability.
|
|
10
10
|
|
|
11
11
|
Set `OPENAI_API_KEY`, then paste this customer support agent into a Ruby console or file. It selects a model directly and exposes one validated application tool:
|
|
12
12
|
|
|
@@ -51,19 +51,33 @@ end
|
|
|
51
51
|
|
|
52
52
|
## How the pieces fit
|
|
53
53
|
|
|
54
|
+
One agent is the smallest useful LittleGhost application. A request creates a run, the agent may call a tool or delegate to a subagent, and the run records the outcome:
|
|
55
|
+
|
|
54
56
|
```text
|
|
55
57
|
provider connections + model selections ──> ModelResolver ──> provider
|
|
56
58
|
│
|
|
57
59
|
request ──> CustomerSupportAgent ──> HelpCenterLookupTool
|
|
58
60
|
│
|
|
59
61
|
└────────> ResearchAgent subagent
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
When one model loop is not enough, LittleGhost calls the larger callable unit an **assembly**. An assembly may be one agent or a coordinated group that still looks like one agent to its caller:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
request ──> CustomerSupportAgent
|
|
60
68
|
|
|
61
69
|
request ──> ResponseWorkflow ──> ResearchAgent ──> CustomerSupportAgent
|
|
70
|
+
|
|
71
|
+
request ──> ProblemSolverSwarm ──> TriageAgent ──handoff──> BillingAgent
|
|
72
|
+
|
|
73
|
+
request ──> SupportFlowGraph ──> TriageAgent ──edge──> ResponseAgent
|
|
62
74
|
```
|
|
63
75
|
|
|
64
|
-
|
|
76
|
+
A `Workflow` uses ordinary Ruby to enforce ordering and branching. A `Swarm` lets configured agents hand the request directly to one another. A `Graph` follows application-declared nodes and edges when the allowed paths should be visible in advance. These types share `ask` and `stream_ask`, so callers can depend on one entrypoint contract while the implementation grows.
|
|
77
|
+
|
|
78
|
+
Class definitions are the default way to organize agents and assemblies. Builder objects support definitions whose participants or topology are discovered at runtime; the Core Concepts guide introduces them after the class-based forms.
|
|
65
79
|
|
|
66
|
-
Each top-level execution owns
|
|
80
|
+
Each top-level execution owns one run lifecycle. The run checkpoints session state, closes its resources, aggregates usage, and emits framework events regardless of which assembly type is the entrypoint.
|
|
67
81
|
|
|
68
82
|
## Installation and configuration
|
|
69
83
|
|
|
@@ -104,12 +118,12 @@ By default, LittleGhost maps `default` to GPT-5.6 Luna. It configures convention
|
|
|
104
118
|
|
|
105
119
|
Applications that need custom routing can subclass `LittleGhost::ModelResolver` and install the class with `config.model_resolver`. A custom resolver owns its profiles and default role; configuring `models`, `models_path`, or `default_model` at the same time is an error. Provider configuration remains available to the resolver.
|
|
106
120
|
|
|
107
|
-
LittleGhost runs inside the surrounding Ruby process; it does not prescribe an HTTP server, CLI, job system, or application layout. `config/little_ghost`, `app/agents`, `app/prompts`, `app/tools`, and `app/skills` are optional conventions.
|
|
121
|
+
LittleGhost runs inside the surrounding Ruby process; it does not prescribe an HTTP server, CLI, job system, or application layout. `config/little_ghost`, `app/agents`, `app/assemblies`, `app/prompts`, `app/tools`, and `app/skills` are optional conventions. Keep agent classes in `app/agents`; workflow, swarm, and graph classes conventionally live in `app/assemblies`. Names should reveal the type, such as `DevelopmentWorkflow`, `ProblemSolverSwarm`, or `SupportFlowGraph`.
|
|
108
122
|
|
|
109
123
|
## Documentation
|
|
110
124
|
|
|
111
125
|
- [Getting Started](docs/guides/getting_started.md) builds and streams the customer support example.
|
|
112
|
-
- [Core Concepts](docs/guides/core_concepts.md)
|
|
126
|
+
- [Core Concepts](docs/guides/core_concepts.md) starts with one agent, then introduces assemblies, delegation, workflows, swarms, graphs, and dynamic builders.
|
|
113
127
|
- [API reference](rdoc-ref:LittleGhost) covers exact signatures, options, and lifecycle behavior.
|
|
114
128
|
|
|
115
129
|
## Contributing
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Core Concepts
|
|
2
2
|
|
|
3
|
-
|
|
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
4
|
|
|
5
5
|
```text
|
|
6
6
|
shared configuration
|
|
@@ -12,9 +12,31 @@ one request
|
|
|
12
12
|
│ ├── HelpCenterLookupTool
|
|
13
13
|
│ └── ResearchAgent subagent (model-directed)
|
|
14
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:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
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."
|
|
28
|
+
tools HelpCenterLookupTool
|
|
29
|
+
end
|
|
30
|
+
```
|
|
31
|
+
|
|
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.
|
|
33
|
+
|
|
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.
|
|
15
35
|
|
|
16
|
-
|
|
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
|
|
18
40
|
```
|
|
19
41
|
|
|
20
42
|
## Models can be selected directly or by role
|
|
@@ -60,37 +82,26 @@ Dotted roles inherit from the nearest registered parent. `ResearchAgent` can req
|
|
|
60
82
|
|
|
61
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.
|
|
62
84
|
|
|
63
|
-
##
|
|
64
|
-
|
|
65
|
-
An agent class declares application behavior:
|
|
66
|
-
|
|
67
|
-
```ruby
|
|
68
|
-
class CustomerSupportAgent < LittleGhost::Agent
|
|
69
|
-
description "Answers customer support questions."
|
|
70
|
-
model "customer_support"
|
|
71
|
-
system_prompt "Answer clearly. Check the help center before stating company guidance."
|
|
72
|
-
tools HelpCenterLookupTool
|
|
73
|
-
subagent ResearchAgent, kind: "research"
|
|
74
|
-
end
|
|
75
|
-
```
|
|
85
|
+
## Runs own top-level lifecycle
|
|
76
86
|
|
|
77
|
-
|
|
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.
|
|
78
88
|
|
|
79
|
-
|
|
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.
|
|
80
90
|
|
|
81
|
-
|
|
91
|
+
Long-lived services can supervise a run without making their request thread own its execution:
|
|
82
92
|
|
|
83
93
|
```ruby
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
## Runs own top-level lifecycle
|
|
94
|
+
execution = CustomerSupportAgent.new.start_execution(
|
|
95
|
+
message: "Investigate transfer 481"
|
|
96
|
+
) do |event|
|
|
97
|
+
event_buffer << event
|
|
98
|
+
end
|
|
90
99
|
|
|
91
|
-
|
|
100
|
+
execution.interrupt_response(message: "Include the latest ledger entry")
|
|
101
|
+
run = execution.wait(deadline: Time.now + 30)
|
|
102
|
+
```
|
|
92
103
|
|
|
93
|
-
|
|
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.
|
|
94
105
|
|
|
95
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.
|
|
96
107
|
|
|
@@ -116,8 +127,31 @@ end
|
|
|
116
127
|
|
|
117
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.
|
|
118
129
|
|
|
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.
|
|
131
|
+
|
|
119
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.
|
|
120
133
|
|
|
134
|
+
## Assemblies let coordination look like one agent
|
|
135
|
+
|
|
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.
|
|
137
|
+
|
|
138
|
+
When a feature needs several agents, three coordination classes preserve that same caller interface:
|
|
139
|
+
|
|
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.
|
|
143
|
+
|
|
144
|
+
```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?")
|
|
149
|
+
```
|
|
150
|
+
|
|
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.
|
|
152
|
+
|
|
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.
|
|
154
|
+
|
|
121
155
|
## Subagents are model-directed delegation
|
|
122
156
|
|
|
123
157
|
Declaring `ResearchAgent` as a subagent gives `CustomerSupportAgent` a configured set of tools for spawning, messaging, interrupting, waiting for, and listing research work:
|
|
@@ -162,23 +196,115 @@ class ResponseWorkflow < LittleGhost::Workflow
|
|
|
162
196
|
end
|
|
163
197
|
```
|
|
164
198
|
|
|
165
|
-
`#invoke` builds a lazy agent
|
|
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.
|
|
214
|
+
|
|
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.
|
|
166
216
|
|
|
167
|
-
A workflow
|
|
217
|
+
A workflow has the same entrypoint API as an agent:
|
|
168
218
|
|
|
169
219
|
```ruby
|
|
170
|
-
|
|
171
|
-
run = runtime.build_run(
|
|
172
|
-
{message: "Review this unusual refund request"},
|
|
173
|
-
agent_class: CustomerSupportAgent,
|
|
174
|
-
entrypoint_class: ResponseWorkflow
|
|
175
|
-
).call
|
|
220
|
+
run = ResponseWorkflow.ask("Review this unusual refund request")
|
|
176
221
|
|
|
177
222
|
puts run.response
|
|
178
223
|
```
|
|
179
224
|
|
|
180
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.
|
|
181
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:
|
|
230
|
+
|
|
231
|
+
```ruby
|
|
232
|
+
class ProblemSolverSwarm < LittleGhost::Swarm
|
|
233
|
+
member TriageAgent
|
|
234
|
+
member BillingAgent
|
|
235
|
+
member AccountAgent
|
|
236
|
+
start TriageAgent
|
|
237
|
+
handoff TriageAgent, to: [BillingAgent, AccountAgent]
|
|
238
|
+
max_steps 12
|
|
239
|
+
max_handoff_repeats 3
|
|
240
|
+
end
|
|
241
|
+
```
|
|
242
|
+
|
|
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.
|
|
246
|
+
|
|
247
|
+
## Graphs guide serial and parallel paths
|
|
248
|
+
|
|
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:
|
|
250
|
+
|
|
251
|
+
```ruby
|
|
252
|
+
class SupportFlowGraph < LittleGhost::Graph
|
|
253
|
+
node :triage, TriageAgent
|
|
254
|
+
node :research, ResearchAgent
|
|
255
|
+
node :verify, VerificationWorkflow
|
|
256
|
+
node :respond, CustomerSupportAgent
|
|
257
|
+
|
|
258
|
+
start :triage
|
|
259
|
+
fork :triage, to: [:research, :verify], max_concurrency: 2
|
|
260
|
+
join [:research, :verify], to: :respond
|
|
261
|
+
finish :respond
|
|
262
|
+
max_steps 12
|
|
263
|
+
end
|
|
264
|
+
```
|
|
265
|
+
|
|
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.
|
|
293
|
+
|
|
294
|
+
## Assemblies can be tools
|
|
295
|
+
|
|
296
|
+
Any assembly instance supports `as_tool`. Agent classes can also declare another assembly as a tool:
|
|
297
|
+
|
|
298
|
+
```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.
|
|
307
|
+
|
|
182
308
|
## Structured results separate data from prose
|
|
183
309
|
|
|
184
310
|
An agent that feeds application code can declare a strict JSON object schema:
|
|
@@ -209,15 +335,18 @@ Use structured results when code consumes fields. Keep ordinary text when a huma
|
|
|
209
335
|
|
|
210
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.
|
|
211
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
|
+
|
|
212
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.
|
|
213
341
|
|
|
214
342
|
## Keep the boundary visible
|
|
215
343
|
|
|
216
|
-
The core design can be summarized as
|
|
344
|
+
The core design can be summarized as five choices:
|
|
217
345
|
|
|
218
346
|
- Put shared construction and provider policy in configuration; use inline declarations or independent YAML files according to the application's needs.
|
|
219
347
|
- Put model behavior and available capabilities on agent classes.
|
|
220
348
|
- Put privileged application operations behind narrow, authorized tools.
|
|
221
|
-
- Put
|
|
349
|
+
- Put imperative ordering in workflows, dynamic peer routing in swarms, and guided routing in graphs.
|
|
350
|
+
- Leave addressable background delegation to subagents.
|
|
222
351
|
|
|
223
|
-
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::Agent`, `LittleGhost::Tool`, `LittleGhost::Workflow`, and `LittleGhost::ModelResolver`.
|
|
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`.
|
|
@@ -120,7 +120,7 @@ Both calls use the same agent definition and active LittleGhost configuration. C
|
|
|
120
120
|
|
|
121
121
|
## Add a model role when the application grows
|
|
122
122
|
|
|
123
|
-
Direct targets keep a small application
|
|
123
|
+
Direct targets keep a small application's model choice beside its behavior. A larger application can give the same selection a stable role, then change the underlying provider, model, and defaults without editing each agent class:
|
|
124
124
|
|
|
125
125
|
```ruby
|
|
126
126
|
LittleGhost.configure do |config|
|
|
@@ -155,10 +155,14 @@ end
|
|
|
155
155
|
|
|
156
156
|
Here, `provider` names a configured connection and every other key after `model` is a trusted model setting. Provider connections and model profiles may instead come from independent YAML files under `config/little_ghost`, or from paths selected in `LittleGhost.configure`. Inline declarations take precedence over explicit paths, which take precedence over conventional files; environment-based selection and the built-in default are the final fallback. `LittleGhost::Agent` and `LittleGhost::Configuration` document the complete shapes and precedence.
|
|
157
157
|
|
|
158
|
-
## Fit
|
|
158
|
+
## Fit the agent into your application
|
|
159
159
|
|
|
160
|
-
LittleGhost does not require an application layout. Keep agents and tools beside related application code when your framework or loader already has a home for them. If you want LittleGhost to eager-load
|
|
160
|
+
LittleGhost does not require an application layout. Keep agents and tools beside related application code when your framework or loader already has a home for them. If you want LittleGhost to eager-load definitions, use `app/agents` for agents and `app/tools` for tools.
|
|
161
|
+
|
|
162
|
+
The agent in this guide owns one model loop. A larger feature may coordinate several participants while preserving the same `ask` and `stream_ask` entrypoints. LittleGhost calls any such callable unit an **assembly**. Workflows, swarms, and graphs are three kinds of coordinated assembly, and they conventionally live in `app/assemblies`. Their class names should state the coordination style, such as `DevelopmentWorkflow`, `ProblemSolverSwarm`, or `SupportFlowGraph`.
|
|
163
|
+
|
|
164
|
+
Classes are the default way to organize reusable definitions. Core Concepts first explains when to choose each assembly type, then introduces builders for definitions discovered at runtime.
|
|
161
165
|
|
|
162
166
|
Hosting remains the surrounding application's responsibility. A Rails controller, Rack endpoint, background job, CLI, or another Ruby entrypoint can call the same agent APIs shown above.
|
|
163
167
|
|
|
164
|
-
Read [Core Concepts](core_concepts.md) next
|
|
168
|
+
Read [Core Concepts](core_concepts.md) next. It begins with the agent you built here, then adds model selection, runs, assemblies, subagents, workflows, swarms, graphs, builders, sessions, and the boundaries between them. The API reference covers exact signatures for `LittleGhost::Agent`, `LittleGhost::Tool`, `LittleGhost::Run`, and the coordination classes.
|
|
@@ -30,7 +30,7 @@ module LittleGhost
|
|
|
30
30
|
default: Subagents::Manager::DEFAULT_WAIT_TIMEOUT
|
|
31
31
|
base.class_attribute :subagent_declarations_value, default: []
|
|
32
32
|
base.class_attribute :subagent_resolvers_value, default: []
|
|
33
|
-
base.class_attribute :
|
|
33
|
+
base.class_attribute :assembly_tool_declarations_value, default: []
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
# Exposes delegation declarations on agent classes.
|
|
@@ -95,25 +95,52 @@ module LittleGhost
|
|
|
95
95
|
# conversational history between calls to that tool instance.
|
|
96
96
|
def agent_as_tool(agent_class, name: nil, description: nil, model: nil, tools: nil,
|
|
97
97
|
preserve_context: false)
|
|
98
|
+
assembly_as_tool(
|
|
99
|
+
agent_class,
|
|
100
|
+
name:,
|
|
101
|
+
description:,
|
|
102
|
+
model:,
|
|
103
|
+
tools:,
|
|
104
|
+
preserve_context:
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Exposes an Agent, Workflow, Swarm, or Graph as one ordinary tool.
|
|
109
|
+
# Agent-only +model+ and +tools+ overrides are rejected for composites.
|
|
110
|
+
def assembly_as_tool(assembly_class, name: nil, description: nil, model: nil, tools: nil,
|
|
111
|
+
preserve_context: false)
|
|
112
|
+
unless assembly_class.is_a?(Class) && assembly_class <= Assembly
|
|
113
|
+
raise ConfigurationError, "Delegated assemblies must inherit from LittleGhost::Assembly"
|
|
114
|
+
end
|
|
98
115
|
validate_delegated_tools!(tools)
|
|
116
|
+
if !(assembly_class <= Agent) && (model || tools)
|
|
117
|
+
raise ConfigurationError, "Composite assemblies do not accept delegated model or tool overrides"
|
|
118
|
+
end
|
|
99
119
|
declaration = {
|
|
100
|
-
|
|
101
|
-
name: (name ||
|
|
102
|
-
description: description ||
|
|
120
|
+
assembly: assembly_class,
|
|
121
|
+
name: (name || assembly_class.assembly_id).to_s,
|
|
122
|
+
description: description || assembly_class.description,
|
|
103
123
|
model:,
|
|
104
124
|
tools:,
|
|
105
125
|
preserve_context:
|
|
106
126
|
}
|
|
107
|
-
self.
|
|
127
|
+
self.assembly_tool_declarations_value = [*assembly_tool_declarations, declaration]
|
|
108
128
|
end
|
|
109
129
|
|
|
110
130
|
# Exposes several agent classes as ordinary tools with shared options.
|
|
111
131
|
def agents_as_tools(*agent_classes, **options)
|
|
112
132
|
agent_classes.each { |agent_class| agent_as_tool(agent_class, **options) }
|
|
113
|
-
|
|
133
|
+
assembly_tool_declarations
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Exposes several assembly classes as ordinary tools with shared options.
|
|
137
|
+
def assemblies_as_tools(*assembly_classes, **options)
|
|
138
|
+
assembly_classes.each { |assembly_class| assembly_as_tool(assembly_class, **options) }
|
|
139
|
+
assembly_tool_declarations
|
|
114
140
|
end
|
|
115
141
|
|
|
116
|
-
def
|
|
142
|
+
def assembly_tool_declarations = assembly_tool_declarations_value # :nodoc:
|
|
143
|
+
def agent_tool_declarations = assembly_tool_declarations # :nodoc:
|
|
117
144
|
|
|
118
145
|
private
|
|
119
146
|
|
|
@@ -176,7 +176,8 @@ module LittleGhost
|
|
|
176
176
|
replacement = Tool::ExecutionResult.new(
|
|
177
177
|
content: "#{warning}\n\n#{result.content}",
|
|
178
178
|
status: result.status,
|
|
179
|
-
error: result.error
|
|
179
|
+
error: result.error,
|
|
180
|
+
companion_content: result.companion_content
|
|
180
181
|
)
|
|
181
182
|
Support::Callbacks.replace(payload.merge(result: replacement))
|
|
182
183
|
end
|