little_ghost 0.2.1 → 0.4.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 +72 -74
- data/docs/guides/assemblies.md +286 -0
- data/docs/guides/core_concepts.md +159 -135
- data/docs/guides/getting_started.md +114 -83
- data/docs/guides/production.md +187 -0
- data/docs/guides/prompt_views.md +132 -0
- data/lib/little_ghost/ag_ui/adapter.rb +3 -3
- data/lib/little_ghost/agent/delegation.rb +35 -8
- data/lib/little_ghost/agent/tool_loop.rb +2 -1
- data/lib/little_ghost/agent.rb +280 -326
- data/lib/little_ghost/agent_builder.rb +20 -4
- data/lib/little_ghost/agent_factory.rb +3 -0
- data/lib/little_ghost/{agent_interruptions.rb → agent_interjections.rb} +12 -12
- data/lib/little_ghost/assembly.rb +345 -0
- data/lib/little_ghost/assembly_builder.rb +497 -0
- data/lib/little_ghost/assembly_execution.rb +535 -0
- data/lib/little_ghost/configuration.rb +263 -39
- data/lib/little_ghost/content.rb +5 -5
- data/lib/little_ghost/data_map.rb +209 -0
- data/lib/little_ghost/errors.rb +10 -2
- data/lib/little_ghost/execution.rb +206 -0
- data/lib/little_ghost/graph.rb +930 -0
- data/lib/little_ghost/message.rb +4 -4
- data/lib/little_ghost/model_resolver.rb +2 -2
- data/lib/little_ghost/prompt_resolver.rb +2 -0
- data/lib/little_ghost/run.rb +190 -64
- data/lib/little_ghost/run_context.rb +33 -20
- data/lib/little_ghost/run_result.rb +22 -11
- data/lib/little_ghost/runtime/hook.rb +9 -4
- data/lib/little_ghost/runtime.rb +134 -36
- data/lib/little_ghost/sandbox.rb +1 -1
- data/lib/little_ghost/session.rb +12 -23
- 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/subagents/manager.rb +42 -42
- 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 +439 -0
- data/lib/little_ghost/tool.rb +88 -20
- data/lib/little_ghost/tools/write_todos.rb +6 -1
- data/lib/little_ghost/tracing/open_telemetry.rb +14 -3
- data/lib/little_ghost/unrestricted_sandbox.rb +1 -1
- data/lib/little_ghost/version.rb +1 -1
- data/lib/little_ghost/workflow.rb +224 -90
- data/lib/little_ghost.rb +36 -25
- metadata +17 -5
|
@@ -1,148 +1,156 @@
|
|
|
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
|
-
one deterministic request
|
|
17
|
-
└── Run ──> ResponseWorkflow ──> ResearchAgent ──> CustomerSupportAgent
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Models can be selected directly or by role
|
|
21
|
-
|
|
22
|
-
An agent can name a canonical target directly when the choice belongs beside its behavior:
|
|
3
|
+
Build one model-driven behavior in a Ruby class, then call it like Ruby. That is the idea LittleGhost grows from.
|
|
23
4
|
|
|
24
5
|
```ruby
|
|
25
6
|
class CustomerSupportAgent < LittleGhost::Agent
|
|
26
|
-
model "openai
|
|
7
|
+
model "openrouter:openai/gpt-5.6-luna"
|
|
8
|
+
system_prompt "Answer customer questions clearly."
|
|
9
|
+
tools HelpCenterLookupTool
|
|
27
10
|
end
|
|
11
|
+
|
|
12
|
+
run = CustomerSupportAgent.ask("Where is my order?")
|
|
13
|
+
run.response
|
|
28
14
|
```
|
|
29
15
|
|
|
30
|
-
|
|
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.
|
|
31
17
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
18
|
+
## An Agent owns one model loop
|
|
19
|
+
|
|
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.
|
|
21
|
+
|
|
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.
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
CustomerSupportAgent
|
|
26
|
+
├── model selection
|
|
27
|
+
├── system prompt
|
|
28
|
+
├── HelpCenterLookupTool
|
|
29
|
+
└── limits and optional capabilities
|
|
36
30
|
```
|
|
37
31
|
|
|
38
|
-
|
|
32
|
+
An Agent can return text or checked, structured data. Later, you can add streaming, sessions, or callbacks. None of them are required to begin.
|
|
33
|
+
|
|
34
|
+
## A Tool connects the model to Ruby
|
|
35
|
+
|
|
36
|
+
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.
|
|
39
37
|
|
|
40
38
|
```ruby
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
39
|
+
class HelpCenterLookupTool < LittleGhost::Tool
|
|
40
|
+
description "Look up a help center entry by topic."
|
|
41
|
+
input_schema(
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {topic: {type: "string"}},
|
|
44
|
+
required: ["topic"],
|
|
45
|
+
additionalProperties: false
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def call(input)
|
|
49
|
+
{"refunds" => "Refunds are available within 30 days."}
|
|
50
|
+
.fetch(input.fetch("topic"))
|
|
51
|
+
end
|
|
48
52
|
end
|
|
49
53
|
```
|
|
50
54
|
|
|
55
|
+
LittleGhost checks the model's arguments, calls the tool, and gives the result back to the model. The schema checks shape, not permission. Authorize sensitive reads and actions inside the tool with trusted application context.
|
|
56
|
+
|
|
57
|
+
## A Run owns one top-level execution
|
|
58
|
+
|
|
59
|
+
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.
|
|
60
|
+
|
|
51
61
|
```ruby
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
62
|
+
run = CustomerSupportAgent.ask("Where is order 481?")
|
|
63
|
+
|
|
64
|
+
run.completed? # => true
|
|
65
|
+
run.response
|
|
66
|
+
# One possible response: Order 481 is out for delivery.
|
|
67
|
+
run.usage # => normalized token usage
|
|
68
|
+
run.result # => the complete LittleGhost::RunResult
|
|
55
69
|
```
|
|
56
70
|
|
|
57
|
-
|
|
71
|
+
The Agent defines reusable behavior; the Run records what happened this time.
|
|
58
72
|
|
|
59
|
-
|
|
73
|
+
### Follow one request
|
|
60
74
|
|
|
61
|
-
|
|
75
|
+
One Run owns the trip from request to result:
|
|
62
76
|
|
|
63
|
-
|
|
77
|
+
```text
|
|
78
|
+
Run
|
|
79
|
+
├── Invocation: caller input, history, and application context
|
|
80
|
+
├── RunContext: mutable working state for this execution
|
|
81
|
+
└── Agent and Tools ──> RunResult
|
|
82
|
+
```
|
|
64
83
|
|
|
65
|
-
An
|
|
84
|
+
An **Invocation** is the request in LittleGhost's standard shape. Its `context` contains current request values supplied by your application. A Tool can read those values through `run.invocation.context` when it authorizes work.
|
|
66
85
|
|
|
67
|
-
|
|
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
|
-
```
|
|
86
|
+
The **RunContext** carries mutable working state in `context.state`. At the top level, saved Session state is loaded first, then current Invocation context is added. Child Assemblies may receive a copy, a mapped value, or no context at all. Recheck saved values before using them for permission decisions.
|
|
76
87
|
|
|
77
|
-
|
|
88
|
+
A Tool's **Binding** gives the Tool access to objects created for this run, including the Agent, Run, workspace, and sandbox. These objects are separate from the arguments chosen by the model.
|
|
78
89
|
|
|
79
|
-
|
|
90
|
+
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.
|
|
80
91
|
|
|
81
|
-
|
|
92
|
+
### See how a call ended
|
|
82
93
|
|
|
83
|
-
|
|
84
|
-
run = CustomerSupportAgent.ask("Can I get a refund?")
|
|
85
|
-
run.response # final text from the top-level execution
|
|
86
|
-
run.result.output # text, or a validated structured value when declared
|
|
87
|
-
```
|
|
94
|
+
Top-level calls normally return a Run, even when execution fails. The terminal event carries the same outcome when you stream:
|
|
88
95
|
|
|
89
|
-
|
|
96
|
+
| What happened | Run outcome | Terminal event | What Ruby does |
|
|
97
|
+
| --- | --- | --- | --- |
|
|
98
|
+
| The assembly completed | `completed` | `:run_stop` | Returns the Run |
|
|
99
|
+
| Model, provider, or assembly execution failed | `failed` | `:run_error` | Returns the Run; inspect `run.error` |
|
|
100
|
+
| The deadline stopped work | `partial` | `:run_partial` | Returns the Run with any response produced so far |
|
|
101
|
+
| Cancellation stopped work | `cancelled` | `:run_cancel` | Returns the Run without a response |
|
|
102
|
+
| Tool input or a `ToolError` failed | The model may recover | No terminal event by itself | Gives a safe error result back to the model |
|
|
103
|
+
| Input, configuration, or resources failed before a Run could start | No Run exists | None | Raises the exception |
|
|
90
104
|
|
|
91
|
-
|
|
105
|
+
Unexpected Tool exception messages are hidden from the model. The original exception remains available to trusted application callbacks and diagnostics.
|
|
92
106
|
|
|
93
|
-
|
|
107
|
+
Failures while closing resources, delivering events, or reporting instrumentation sit outside the normal result path. They raise a Ruby exception because LittleGhost can no longer promise that it delivered a clean ending. [Running in Production](production.md) covers that boundary where applications supervise and shut down work.
|
|
94
108
|
|
|
95
|
-
An
|
|
109
|
+
## An Assembly can look like one Agent
|
|
96
110
|
|
|
97
|
-
|
|
111
|
+
One model loop is not always enough. LittleGhost calls any unit that a caller can invoke like an Agent an **Assembly**.
|
|
98
112
|
|
|
99
|
-
|
|
113
|
+
An Agent is the smallest Assembly. Workflow, Swarm, and Graph coordinate several participants while preserving the same entrypoints:
|
|
100
114
|
|
|
101
115
|
```ruby
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
properties: {topic: {type: "string"}},
|
|
107
|
-
required: ["topic"],
|
|
108
|
-
additionalProperties: false
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
def call(input)
|
|
112
|
-
HelpCenterRepository.fetch(input.fetch("topic"))
|
|
113
|
-
end
|
|
114
|
-
end
|
|
116
|
+
CustomerSupportAgent.ask(question)
|
|
117
|
+
ResponseWorkflow.ask(question)
|
|
118
|
+
ProblemSolverSwarm.ask(question)
|
|
119
|
+
SupportFlowGraph.ask(question)
|
|
115
120
|
```
|
|
116
121
|
|
|
117
|
-
|
|
122
|
+
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.
|
|
118
123
|
|
|
119
|
-
|
|
124
|
+
## Choose who controls the next step
|
|
120
125
|
|
|
121
|
-
|
|
126
|
+
The coordination types differ mainly in who decides what happens next:
|
|
122
127
|
|
|
123
|
-
|
|
128
|
+
| Need | Choose | Who controls the next step? |
|
|
129
|
+
| --- | --- | --- |
|
|
130
|
+
| One model-driven behavior | Agent | The active model loop |
|
|
131
|
+
| A model should delegate a named task | Subagent | The parent model |
|
|
132
|
+
| Ruby should enforce ordering or branching | Workflow | The workflow's Ruby code |
|
|
133
|
+
| Specialists should choose permitted handoffs | Swarm | The active agent |
|
|
134
|
+
| Allowed routes should be visible in advance | Graph | Declared nodes and edges |
|
|
124
135
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
model "customer_support.research"
|
|
129
|
-
system_prompt "Return a concise evidence summary."
|
|
130
|
-
end
|
|
136
|
+
### Subagents bring in a specialist
|
|
137
|
+
|
|
138
|
+
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.
|
|
131
139
|
|
|
140
|
+
```ruby
|
|
132
141
|
class CustomerSupportAgent < LittleGhost::Agent
|
|
133
|
-
model "
|
|
134
|
-
tools HelpCenterLookupTool
|
|
142
|
+
model "openrouter:openai/gpt-5.6-luna"
|
|
135
143
|
subagent ResearchAgent, kind: "research"
|
|
136
144
|
end
|
|
137
145
|
```
|
|
138
146
|
|
|
139
|
-
|
|
147
|
+
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.
|
|
140
148
|
|
|
141
|
-
|
|
149
|
+
### Workflows make Ruby the coordinator
|
|
142
150
|
|
|
143
|
-
|
|
151
|
+
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.
|
|
144
152
|
|
|
145
|
-
|
|
153
|
+
`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.
|
|
146
154
|
|
|
147
155
|
```ruby
|
|
148
156
|
class ResponseWorkflow < LittleGhost::Workflow
|
|
@@ -150,9 +158,7 @@ class ResponseWorkflow < LittleGhost::Workflow
|
|
|
150
158
|
|
|
151
159
|
def perform
|
|
152
160
|
research = invoke(ResearchAgent).output
|
|
153
|
-
|
|
154
161
|
invoke CustomerSupportAgent, input: <<~PROMPT
|
|
155
|
-
Customer request:
|
|
156
162
|
#{input.text}
|
|
157
163
|
|
|
158
164
|
Research:
|
|
@@ -162,62 +168,80 @@ class ResponseWorkflow < LittleGhost::Workflow
|
|
|
162
168
|
end
|
|
163
169
|
```
|
|
164
170
|
|
|
165
|
-
|
|
171
|
+
Workflow children receive the caller's history and application context by default. Pass `history: []`, `context: {}`, or redacted values when a participant should receive less.
|
|
166
172
|
|
|
167
|
-
|
|
173
|
+
### Swarms let agents hand work to one another
|
|
174
|
+
|
|
175
|
+
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.
|
|
168
176
|
|
|
169
177
|
```ruby
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
+
class ProblemSolverSwarm < LittleGhost::Swarm
|
|
179
|
+
member TriageAgent
|
|
180
|
+
member BillingAgent
|
|
181
|
+
member AccountAgent
|
|
182
|
+
|
|
183
|
+
start TriageAgent
|
|
184
|
+
handoff TriageAgent, to: [BillingAgent, AccountAgent]
|
|
185
|
+
end
|
|
178
186
|
```
|
|
179
187
|
|
|
180
|
-
|
|
188
|
+
A Swarm is intentionally agent-to-agent. Its members are Agents, not other kinds of Assembly. Caller history and application context stay hidden unless a member opts in. Treat every handoff message as untrusted model input.
|
|
181
189
|
|
|
182
|
-
|
|
190
|
+
### Graphs make routes visible
|
|
183
191
|
|
|
184
|
-
|
|
192
|
+
A **Graph** connects named Assembly nodes with declared edges. Nodes can contain Agents, Workflows, Swarms, or other Graphs.
|
|
185
193
|
|
|
186
194
|
```ruby
|
|
187
|
-
class
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
195
|
+
class SupportFlowGraph < LittleGhost::Graph
|
|
196
|
+
node :triage, TriageAgent
|
|
197
|
+
node :billing, BillingAgent
|
|
198
|
+
node :general, CustomerSupportAgent
|
|
199
|
+
node :respond, CustomerSupportAgent
|
|
200
|
+
|
|
201
|
+
start :triage
|
|
202
|
+
edge :triage, :billing do |state|
|
|
203
|
+
state.result(:triage).output == "billing"
|
|
204
|
+
end
|
|
205
|
+
edge :triage, :general
|
|
206
|
+
edge :billing, :respond
|
|
207
|
+
edge :general, :respond
|
|
208
|
+
finish :respond
|
|
201
209
|
end
|
|
202
210
|
```
|
|
203
211
|
|
|
204
|
-
|
|
212
|
+
Graph nodes do not receive caller history or application context unless they opt in. They still receive the original request and the outputs routed to them. Use edge input mappers to choose or redact what moves forward.
|
|
213
|
+
|
|
214
|
+
## Class definitions first, builders when needed
|
|
215
|
+
|
|
216
|
+
Named classes are the default way to organize reusable behavior. They are readable, load through normal Ruby conventions, and give the coordination style a visible name such as `ResponseWorkflow` or `SupportFlowGraph`.
|
|
205
217
|
|
|
206
|
-
|
|
218
|
+
Every assembly class can also produce a mutable builder:
|
|
207
219
|
|
|
208
|
-
|
|
220
|
+
```ruby
|
|
221
|
+
graph = SupportFlowGraph.to_builder
|
|
222
|
+
graph.node :audit, AuditAgent
|
|
223
|
+
graph.edge :respond, :audit
|
|
224
|
+
graph.finish :audit
|
|
225
|
+
graph.validate!
|
|
226
|
+
run = graph.ask("Review order 481")
|
|
227
|
+
```
|
|
209
228
|
|
|
210
|
-
|
|
229
|
+
Use a builder when trusted application configuration decides the participants or routes. Each run gets a fixed copy of the builder as it looked when the run began, so later edits affect later runs. Ruby callbacks still see any application objects they captured.
|
|
211
230
|
|
|
212
|
-
|
|
231
|
+
## One result, including the journey
|
|
213
232
|
|
|
214
|
-
|
|
233
|
+
Every assembly produces the same top-level `Run` and final `RunResult`. Composite assemblies also keep a size-limited record of the participants that ran:
|
|
234
|
+
|
|
235
|
+
```ruby
|
|
236
|
+
run = SupportFlowGraph.ask("Why was I charged twice?")
|
|
237
|
+
|
|
238
|
+
run.response
|
|
239
|
+
run.result.steps
|
|
240
|
+
run.result.trajectory.transitions
|
|
241
|
+
```
|
|
215
242
|
|
|
216
|
-
|
|
243
|
+
This shows callers which participants ran without including raw provider responses. A Swarm or Graph may hide intermediate model events from the public stream so the response stays coherent. That is a presentation choice, not a privacy boundary: routed outputs and step summaries still exist.
|
|
217
244
|
|
|
218
|
-
|
|
219
|
-
- Put model behavior and available capabilities on agent classes.
|
|
220
|
-
- Put privileged application operations behind narrow, authorized tools.
|
|
221
|
-
- Put mandatory ordering in workflows; leave optional delegation to subagents.
|
|
245
|
+
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.
|
|
222
246
|
|
|
223
|
-
|
|
247
|
+
Continue with [Compose Agents](assemblies.md) to put several agents to work together. If you are ready to connect the feature to a real application, jump to [Running in Production](production.md).
|