little_ghost 0.1.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 +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +122 -0
- data/docs/guides/Core Concepts.md +203 -0
- data/docs/guides/Getting Started.md +187 -0
- data/lib/little_ghost/ag_ui/adapter.rb +194 -0
- data/lib/little_ghost/ag_ui.rb +5 -0
- data/lib/little_ghost/agent/context_management.rb +285 -0
- data/lib/little_ghost/agent/delegation.rb +128 -0
- data/lib/little_ghost/agent/skills.rb +96 -0
- data/lib/little_ghost/agent/tool_loop.rb +239 -0
- data/lib/little_ghost/agent.rb +2111 -0
- data/lib/little_ghost/agent_builder.rb +191 -0
- data/lib/little_ghost/agent_interruptions.rb +197 -0
- data/lib/little_ghost/configuration.rb +337 -0
- data/lib/little_ghost/content.rb +324 -0
- data/lib/little_ghost/default_model_registry.rb +71 -0
- data/lib/little_ghost/errors.rb +48 -0
- data/lib/little_ghost/events.rb +264 -0
- data/lib/little_ghost/execution_state.rb +58 -0
- data/lib/little_ghost/instrumentation.rb +475 -0
- data/lib/little_ghost/invocation.rb +285 -0
- data/lib/little_ghost/lookup.rb +37 -0
- data/lib/little_ghost/mcp/client.rb +396 -0
- data/lib/little_ghost/mcp.rb +5 -0
- data/lib/little_ghost/message.rb +75 -0
- data/lib/little_ghost/model.rb +88 -0
- data/lib/little_ghost/model_capabilities.rb +126 -0
- data/lib/little_ghost/model_registry.rb +173 -0
- data/lib/little_ghost/model_request.rb +107 -0
- data/lib/little_ghost/model_response.rb +48 -0
- data/lib/little_ghost/path_set.rb +32 -0
- data/lib/little_ghost/prompt_resolver.rb +251 -0
- data/lib/little_ghost/providers/bedrock.rb +506 -0
- data/lib/little_ghost/providers/http_transport.rb +149 -0
- data/lib/little_ghost/providers/open_router.rb +171 -0
- data/lib/little_ghost/providers/openai.rb +27 -0
- data/lib/little_ghost/providers/openai_compatible.rb +745 -0
- data/lib/little_ghost/providers/sse_parser.rb +35 -0
- data/lib/little_ghost/run.rb +607 -0
- data/lib/little_ghost/run_context.rb +129 -0
- data/lib/little_ghost/run_result.rb +111 -0
- data/lib/little_ghost/runtime/hook.rb +31 -0
- data/lib/little_ghost/runtime.rb +392 -0
- data/lib/little_ghost/sandbox.rb +138 -0
- data/lib/little_ghost/session.rb +229 -0
- data/lib/little_ghost/session_store.rb +96 -0
- data/lib/little_ghost/session_stores/agent_core_memory.rb +1086 -0
- data/lib/little_ghost/session_stores/memory.rb +86 -0
- data/lib/little_ghost/skills/catalog.rb +283 -0
- data/lib/little_ghost/skills/skill.rb +60 -0
- data/lib/little_ghost/skills.rb +4 -0
- data/lib/little_ghost/stream_event.rb +49 -0
- data/lib/little_ghost/structured_output.rb +126 -0
- data/lib/little_ghost/subagents/agent_path.rb +63 -0
- data/lib/little_ghost/subagents/definition.rb +42 -0
- data/lib/little_ghost/subagents/manager.rb +1615 -0
- data/lib/little_ghost/support/callbacks.rb +151 -0
- data/lib/little_ghost/support/cancellation_token.rb +86 -0
- data/lib/little_ghost/support/class_attributes.rb +40 -0
- data/lib/little_ghost/support/content_capture.rb +150 -0
- data/lib/little_ghost/support/executor.rb +75 -0
- data/lib/little_ghost/support/interruptible_stream.rb +103 -0
- data/lib/little_ghost/support/loader.rb +263 -0
- data/lib/little_ghost/support/output_truncation.rb +71 -0
- data/lib/little_ghost/support/redactor.rb +66 -0
- data/lib/little_ghost/support.rb +34 -0
- data/lib/little_ghost/tool.rb +448 -0
- data/lib/little_ghost/tool_execution.rb +59 -0
- data/lib/little_ghost/tool_registry.rb +156 -0
- data/lib/little_ghost/tools/filesystem.rb +119 -0
- data/lib/little_ghost/tools/shell.rb +45 -0
- data/lib/little_ghost/tools/write_todos.rb +91 -0
- data/lib/little_ghost/tools.rb +6 -0
- data/lib/little_ghost/tracing/open_telemetry.rb +517 -0
- data/lib/little_ghost/unrestricted_sandbox.rb +306 -0
- data/lib/little_ghost/usage.rb +47 -0
- data/lib/little_ghost/version.rb +6 -0
- data/lib/little_ghost/workflow.rb +351 -0
- data/lib/little_ghost/workspace.rb +31 -0
- data/lib/little_ghost.rb +120 -0
- metadata +225 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 16d2003f8e5b79a2532ab16b9a0a1d95a9ee65b21025371290aeccacacd0f8a1
|
|
4
|
+
data.tar.gz: 0c4db0710a8650ebb1ae159bc59a62078c442a035b64c3f549cbfb686c881c32
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c435a0f6ef9b9d4508d66f72610b32b58f45ed724af5d8b9fbf8cc20fc27eaa8a41dcffc1c4fe0e109fbfc3603dffdf393f0bc38444a554009b41d8302b74f1a
|
|
7
|
+
data.tar.gz: 1945188f5019cd2b115f74df481a344124d793eaa74ace0e6efc57b84d9bc117ee20879a2764818c78918e1dcd0609f3c1230e27b9a6b2885b4db4288a9e8b6b
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matt Robinson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
data/README.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Build AI features with LittleGhost
|
|
2
|
+
|
|
3
|
+
<hr>
|
|
4
|
+
|
|
5
|
+
**Growing in public.** LittleGhost is under active development, and interfaces may evolve between releases. Pin the gem version and review the release notes when upgrading.
|
|
6
|
+
|
|
7
|
+
<hr>
|
|
8
|
+
|
|
9
|
+
Bring agents and agentic workflows into an existing Ruby system, or use them as the core of a dedicated AI service. LittleGhost brings model providers, tools, streaming, sessions, delegation, deterministic composition, and observability together behind one coherent set of Ruby APIs.
|
|
10
|
+
|
|
11
|
+
Here is the shape of a small customer support agent. `CustomerSupportModels` keeps provider details out of agent behavior, `HelpCenterLookupTool` exposes one narrow, validated help center lookup, and `CustomerSupportAgent` brings them together:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
require "little_ghost"
|
|
15
|
+
|
|
16
|
+
class CustomerSupportModels < LittleGhost::ModelRegistry
|
|
17
|
+
def initialize
|
|
18
|
+
super
|
|
19
|
+
provider(:openai) do |model:, **|
|
|
20
|
+
LittleGhost::Providers::OpenAI.new(
|
|
21
|
+
api_key: ENV.fetch("OPENAI_API_KEY"),
|
|
22
|
+
model:
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
profile "customer_support", provider: :openai, model: "gpt-5"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class HelpCenterLookupTool < LittleGhost::Tool
|
|
30
|
+
description "Look up a help center entry by topic."
|
|
31
|
+
input_schema(
|
|
32
|
+
type: "object",
|
|
33
|
+
properties: {topic: {type: "string"}},
|
|
34
|
+
required: ["topic"],
|
|
35
|
+
additionalProperties: false
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def call(input)
|
|
39
|
+
{"refunds" => "Refunds are available within 30 days."}
|
|
40
|
+
.fetch(input.fetch("topic"), "No help center entry found.")
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class CustomerSupportAgent < LittleGhost::Agent
|
|
45
|
+
description "Answers customer support questions."
|
|
46
|
+
model "customer_support"
|
|
47
|
+
system_prompt "Answer clearly. Check the help center before stating company guidance."
|
|
48
|
+
tools HelpCenterLookupTool
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
LittleGhost.configure { |config| config.models CustomerSupportModels }
|
|
52
|
+
|
|
53
|
+
run = CustomerSupportAgent.ask("Can I get a refund after two weeks?")
|
|
54
|
+
puts run.response
|
|
55
|
+
# One possible response:
|
|
56
|
+
# Refunds are available within 30 days, so your purchase is eligible.
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The result is a normal Ruby object. You can inspect its outcome, final response, normalized messages, token usage, and error instead of parsing provider-specific payloads.
|
|
60
|
+
|
|
61
|
+
LittleGhost keeps the model loop provider-neutral. Tools can run in parallel, mutating tools can opt into a run-wide exclusive lock, and oversized tool results are truncated to the configured limit before they enter model context. Strict result schemas, cancellation, deadlines, session checkpoints, structured events, and OpenTelemetry integration are available when an application needs them; none are required to define the first agent.
|
|
62
|
+
|
|
63
|
+
The core stays dependency-light, while provider SDKs and deployment choices remain with the application.
|
|
64
|
+
|
|
65
|
+
## How it fits together
|
|
66
|
+
|
|
67
|
+
```text
|
|
68
|
+
application configuration ──> CustomerSupportModels ──> provider
|
|
69
|
+
│
|
|
70
|
+
request ──> CustomerSupportAgent ──> HelpCenterLookupTool
|
|
71
|
+
│
|
|
72
|
+
└────────> ResearchAgent (when delegated)
|
|
73
|
+
|
|
74
|
+
request ──> ResponseWorkflow ──> ResearchAgent ──> CustomerSupportAgent
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Configuration owns shared services such as model registries, sessions, lookup paths, workspaces, sandboxes, and instrumentation. Agent classes own behavior: their logical model role, prompt, tools, limits, structured result, and delegation policy. A tool is a validated boundary around application code. A subagent lets the model delegate work within configured turn, concurrency, and depth limits; a workflow uses ordinary Ruby when your application must choose the sequence.
|
|
78
|
+
|
|
79
|
+
`CustomerSupportAgent.ask(...)` creates a standalone entrypoint, consumes its run, and returns the completed `LittleGhost::Run`. Create an instance explicitly when reusing a runtime or when an interface should render progress as `LittleGhost::StreamEvent` objects:
|
|
80
|
+
|
|
81
|
+
`LittleGhost::Agent.ask("hi")` uses the built-in default model selection and the system prompt `You are a helpful agent.` for a general-purpose agent.
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
CustomerSupportAgent.new.stream_ask("Can I get a refund?").each do |event|
|
|
85
|
+
print event.data[:text] if event.type == :text_delta
|
|
86
|
+
end
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Applications can add a `ResearchAgent` as a subagent for open-ended investigation, or place both agents behind a deterministic `ResponseWorkflow`. The same run lifecycle checkpoints session state, closes owned resources, aggregates usage, and emits framework events in each form.
|
|
90
|
+
|
|
91
|
+
## Installation
|
|
92
|
+
|
|
93
|
+
LittleGhost requires Ruby 3.3 or newer. Add it to your bundle:
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
gem "little_ghost"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Then run `bundle install`. Provider SDKs and OpenTelemetry exporters are optional application dependencies. The built-in OpenAI, OpenAI-compatible, OpenRouter, and Amazon Bedrock integrations normalize their responses into the same LittleGhost protocol.
|
|
100
|
+
|
|
101
|
+
By default, LittleGhost maps the `default` role to GPT-5.6 Terra. It selects the first nonblank API key from `LITTLEGHOST_OPENROUTER_API_KEY`, `LITTLEGHOST_OPENAI_API_KEY`, `OPENROUTER_API_KEY`, and `OPENAI_API_KEY`, in that order. Setting one of these keys authorizes model inputs—including prompts, conversation history, tool data, and attachments—to be sent to the selected external provider. Applications with provider or data-residency requirements should configure a model registry explicitly.
|
|
102
|
+
|
|
103
|
+
By default, structured framework events have no console destination. Hosted applications can emit redacted JSON lines to standard output with `LittleGhost.configure { |config| config.log_events_to :stdout }`; `:stderr` selects standard error instead, and `nil` disables a configured destination. Event emission and severity levels are the same with or without a console destination.
|
|
104
|
+
|
|
105
|
+
Prompts can stay inline while an agent is small, then move into conventional ERB templates under `app/prompts`. Agents and tools under `app/agents` and `app/tools` are loaded from the configured application root, keeping framework setup separate from product behavior.
|
|
106
|
+
|
|
107
|
+
## Documentation
|
|
108
|
+
|
|
109
|
+
- [LittleGhost website](https://mattyr.github.io/little_ghost/) is the quickest way to meet the framework and find your next step.
|
|
110
|
+
- [Getting Started](docs/guides/Getting%20Started.md) builds the customer support example from an empty application and runs it.
|
|
111
|
+
- [Core Concepts](docs/guides/Core%20Concepts.md) explains models, agents, tools, delegation, workflows, sessions, and streaming through the same example.
|
|
112
|
+
- [API reference](rdoc-ref:LittleGhost) covers exact signatures, options, and lifecycle details.
|
|
113
|
+
|
|
114
|
+
LittleGhost is licensed under the MIT License.
|
|
115
|
+
|
|
116
|
+
## Development
|
|
117
|
+
|
|
118
|
+
```sh
|
|
119
|
+
bundle install
|
|
120
|
+
bundle exec rake test
|
|
121
|
+
bundle exec standardrb --no-fix
|
|
122
|
+
```
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Core Concepts
|
|
2
|
+
|
|
3
|
+
LittleGhost gives Ruby software two ways to compose AI behavior. Agents can choose among validated tools and delegated specialists, while agentic workflows keep required ordering and branching under application control. The customer support example makes that boundary visible: CustomerSupportModels chooses provider-backed models, CustomerSupportAgent owns behavior, HelpCenterLookupTool exposes a narrow help center lookup, ResearchAgent handles delegated investigation, and ResponseWorkflow imposes a deterministic sequence when the surrounding system requires one.
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
shared configuration
|
|
7
|
+
└── CustomerSupportModels ── resolves logical roles ──> 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 are selected by role
|
|
21
|
+
|
|
22
|
+
An agent names a logical role such as `customer_support`, not a vendor model. `CustomerSupportModels` maps that stable application vocabulary to a provider and model identifier:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
class CustomerSupportModels < LittleGhost::ModelRegistry
|
|
26
|
+
def initialize
|
|
27
|
+
super
|
|
28
|
+
provider(:openai) do |model:, **|
|
|
29
|
+
LittleGhost::Providers::OpenAI.new(
|
|
30
|
+
api_key: ENV.fetch("OPENAI_API_KEY"),
|
|
31
|
+
model:
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
profile "customer_support", provider: :openai, model: "gpt-5"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
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. Per-invocation profile overrides can vary a request without mutating the registry or agent class. Because an override 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.
|
|
40
|
+
|
|
41
|
+
The provider performs model I/O. The registry resolves application intent into a `LittleGhost::Model`, which carries the provider, settings, metadata, model identifier, and role for a run.
|
|
42
|
+
|
|
43
|
+
## Agents declare behavior
|
|
44
|
+
|
|
45
|
+
An agent class declares application behavior:
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
class CustomerSupportAgent < LittleGhost::Agent
|
|
49
|
+
description "Answers customer support questions."
|
|
50
|
+
model "customer_support"
|
|
51
|
+
system_prompt "Answer clearly. Check the help center before stating company guidance."
|
|
52
|
+
tools HelpCenterLookupTool
|
|
53
|
+
subagent ResearchAgent, kind: "research"
|
|
54
|
+
end
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The class-level DSL is inheritable. It can declare prompts, limits, callbacks, tool classes, structured results, context management, skills, and delegation. A capability mixin may be included in `LittleGhost::Agent`, but its behavior remains inactive until the corresponding DSL is called.
|
|
58
|
+
|
|
59
|
+
`CustomerSupportAgent.ask` creates a standalone, console-friendly entrypoint, builds and consumes a `LittleGhost::Run`, and returns that run. Create `CustomerSupportAgent.new` explicitly to reuse a runtime or call `#stream_ask` for the run's events. Agents built by a runtime are instead scoped to their owning run and return a `LittleGhost::RunResult` from `#call`.
|
|
60
|
+
|
|
61
|
+
That distinction explains two useful return paths:
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
run = CustomerSupportAgent.ask("Can I get a refund?")
|
|
65
|
+
run.response # final text from the top-level execution
|
|
66
|
+
run.result.output # text, or a validated structured value when declared
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Runs own top-level lifecycle
|
|
70
|
+
|
|
71
|
+
A `LittleGhost::Run` owns one top-level agent or workflow execution. It opens the session, workspace, sandbox, entrypoint, and registered resources, then closes owned resources in reverse order.
|
|
72
|
+
|
|
73
|
+
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.
|
|
74
|
+
|
|
75
|
+
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.
|
|
76
|
+
|
|
77
|
+
## Tools are validated application boundaries
|
|
78
|
+
|
|
79
|
+
`HelpCenterLookupTool` exposes exactly one operation to the model:
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
class HelpCenterLookupTool < LittleGhost::Tool
|
|
83
|
+
description "Look up a help center entry by topic."
|
|
84
|
+
input_schema(
|
|
85
|
+
type: "object",
|
|
86
|
+
properties: {topic: {type: "string"}},
|
|
87
|
+
required: ["topic"],
|
|
88
|
+
additionalProperties: false
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def call(input)
|
|
92
|
+
HelpCenterRepository.fetch(input.fetch("topic"))
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
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.
|
|
98
|
+
|
|
99
|
+
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.
|
|
100
|
+
|
|
101
|
+
## Subagents are model-directed delegation
|
|
102
|
+
|
|
103
|
+
Declaring `ResearchAgent` as a subagent gives `CustomerSupportAgent` a configured set of tools for spawning, messaging, interrupting, waiting for, and listing research work:
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
class ResearchAgent < LittleGhost::Agent
|
|
107
|
+
description "Investigates support questions that need broader research."
|
|
108
|
+
model "customer_support.research"
|
|
109
|
+
system_prompt "Return a concise evidence summary."
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
class CustomerSupportAgent < LittleGhost::Agent
|
|
113
|
+
model "customer_support"
|
|
114
|
+
tools HelpCenterLookupTool
|
|
115
|
+
subagent ResearchAgent, kind: "research"
|
|
116
|
+
end
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
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.
|
|
120
|
+
|
|
121
|
+
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.
|
|
122
|
+
|
|
123
|
+
## Workflows are application-directed composition
|
|
124
|
+
|
|
125
|
+
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:
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
class ResponseWorkflow < LittleGhost::Workflow
|
|
129
|
+
private
|
|
130
|
+
|
|
131
|
+
def perform
|
|
132
|
+
research = invoke(ResearchAgent).output
|
|
133
|
+
|
|
134
|
+
invoke CustomerSupportAgent, input: <<~PROMPT
|
|
135
|
+
Customer request:
|
|
136
|
+
#{input.text}
|
|
137
|
+
|
|
138
|
+
Research:
|
|
139
|
+
#{research}
|
|
140
|
+
PROMPT
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`#invoke` builds a lazy agent invocation. Calling `#output` consumes an intermediate invocation; `#perform` must return its final invocation unconsumed so LittleGhost can stream that agent 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.
|
|
146
|
+
|
|
147
|
+
A workflow is an explicit entrypoint on a run:
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
runtime = LittleGhost::Runtime.new(configuration: LittleGhost.configuration)
|
|
151
|
+
run = runtime.build_run(
|
|
152
|
+
{message: "Review this unusual refund request"},
|
|
153
|
+
agent_class: CustomerSupportAgent,
|
|
154
|
+
entrypoint_class: ResponseWorkflow
|
|
155
|
+
).call
|
|
156
|
+
|
|
157
|
+
puts run.response
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
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.
|
|
161
|
+
|
|
162
|
+
## Structured results separate data from prose
|
|
163
|
+
|
|
164
|
+
An agent that feeds application code can declare a strict JSON object schema:
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
class ResearchAgent < LittleGhost::Agent
|
|
168
|
+
model "customer_support.research"
|
|
169
|
+
result_schema(
|
|
170
|
+
{
|
|
171
|
+
type: "object",
|
|
172
|
+
properties: {
|
|
173
|
+
summary: {type: "string"},
|
|
174
|
+
sources: {type: "array", items: {type: "string"}}
|
|
175
|
+
},
|
|
176
|
+
required: %w[summary sources],
|
|
177
|
+
additionalProperties: false
|
|
178
|
+
},
|
|
179
|
+
name: "support_research"
|
|
180
|
+
)
|
|
181
|
+
end
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
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`.
|
|
185
|
+
|
|
186
|
+
Use structured results when code consumes fields. Keep ordinary text when a human is the final consumer.
|
|
187
|
+
|
|
188
|
+
## Sessions preserve conversation, streams expose progress
|
|
189
|
+
|
|
190
|
+
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.
|
|
191
|
+
|
|
192
|
+
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.
|
|
193
|
+
|
|
194
|
+
## Keep the boundary visible
|
|
195
|
+
|
|
196
|
+
The core design can be summarized as four choices:
|
|
197
|
+
|
|
198
|
+
- Put shared construction and provider policy in configuration and model registries.
|
|
199
|
+
- Put model behavior and available capabilities on agent classes.
|
|
200
|
+
- Put privileged application operations behind narrow, authorized tools.
|
|
201
|
+
- Put mandatory ordering in workflows; leave optional delegation to subagents.
|
|
202
|
+
|
|
203
|
+
Return to [Getting Started](Getting%20Started.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::ModelRegistry`.
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Getting Started with LittleGhost
|
|
2
|
+
|
|
3
|
+
This guide builds a small AI customer support feature around an agent that checks an in-memory help center and returns a normal Ruby result. By the end, you will have configured a model provider, declared a validated tool, run `CustomerSupportAgent`, and streamed the same request as events.
|
|
4
|
+
|
|
5
|
+
The example uses OpenAI, but the agent and tool do not depend on that choice. A `ModelRegistry` keeps the provider boundary behind the logical model role `customer_support`.
|
|
6
|
+
|
|
7
|
+
## Before you begin
|
|
8
|
+
|
|
9
|
+
LittleGhost requires Ruby 3.3 or newer. Add the gem to your `Gemfile`:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem "little_ghost"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Install the bundle and provide the credential used by this example:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
$ bundle install
|
|
19
|
+
$ export OPENAI_API_KEY="..."
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Use application-specific secret management outside a local shell. Do not commit provider credentials.
|
|
23
|
+
|
|
24
|
+
## Create the application shape
|
|
25
|
+
|
|
26
|
+
LittleGhost looks for agents, tools, prompts, and skills under conventional application directories. This example needs four files:
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
customer_support_app/
|
|
30
|
+
├── app/
|
|
31
|
+
│ ├── agents/
|
|
32
|
+
│ │ ├── research_agent.rb
|
|
33
|
+
│ │ └── customer_support_agent.rb
|
|
34
|
+
│ └── tools/
|
|
35
|
+
│ └── help_center_lookup_tool.rb
|
|
36
|
+
└── config/
|
|
37
|
+
└── little_ghost.rb
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Run the application from `customer_support_app/`, or set the configuration root explicitly before the first runtime is built. The runtime loads `config/little_ghost.rb` lazily and eager-loads conventional application code.
|
|
41
|
+
|
|
42
|
+
## Configure a logical model role
|
|
43
|
+
|
|
44
|
+
Define `CustomerSupportModels` in `config/little_ghost.rb` and register it with LittleGhost:
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
require "little_ghost"
|
|
48
|
+
|
|
49
|
+
class CustomerSupportModels < LittleGhost::ModelRegistry
|
|
50
|
+
def initialize
|
|
51
|
+
super
|
|
52
|
+
|
|
53
|
+
provider(:openai) do |model:, **|
|
|
54
|
+
LittleGhost::Providers::OpenAI.new(
|
|
55
|
+
api_key: ENV.fetch("OPENAI_API_KEY"),
|
|
56
|
+
model:
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
profile "customer_support",
|
|
61
|
+
provider: :openai,
|
|
62
|
+
model: "gpt-5",
|
|
63
|
+
settings: {temperature: 0.2}
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
LittleGhost.configure do |config|
|
|
68
|
+
config.models CustomerSupportModels
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The agent will ask for the role `customer_support`; it never needs the provider name or provider model identifier. This separation lets an application change providers or override a profile for one invocation without changing agent classes. Treat model settings and profile overrides as trusted application configuration: construct or allowlist them server-side instead of copying unchecked request fields.
|
|
73
|
+
|
|
74
|
+
## Give the agent one validated tool
|
|
75
|
+
|
|
76
|
+
Create `app/tools/help_center_lookup_tool.rb`:
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
class HelpCenterLookupTool < LittleGhost::Tool
|
|
80
|
+
HELP_CENTER_ENTRIES = {
|
|
81
|
+
"refunds" => "Refunds are available within 30 days of purchase.",
|
|
82
|
+
"shipping" => "Standard shipping takes three to five business days."
|
|
83
|
+
}.freeze
|
|
84
|
+
|
|
85
|
+
description "Look up a help center entry by topic."
|
|
86
|
+
input_schema(
|
|
87
|
+
type: "object",
|
|
88
|
+
properties: {
|
|
89
|
+
topic: {type: "string", enum: HELP_CENTER_ENTRIES.keys}
|
|
90
|
+
},
|
|
91
|
+
required: ["topic"],
|
|
92
|
+
additionalProperties: false
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def call(input)
|
|
96
|
+
HELP_CENTER_ENTRIES.fetch(input.fetch("topic"))
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
A tool exposes a name, description, JSON input schema, and implementation. `HelpCenterLookupTool` gets its default model-visible name from the class name. LittleGhost validates input before calling `#call` and turns the returned value into text for model context.
|
|
102
|
+
|
|
103
|
+
This boundary is also the right place for application authorization. If lookup behavior or results depended on an account or tenant, `#call` would verify trusted run context before reading them. A schema validates shape; it does not authorize access.
|
|
104
|
+
|
|
105
|
+
## Define the agents
|
|
106
|
+
|
|
107
|
+
Create `app/agents/research_agent.rb`:
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
class ResearchAgent < LittleGhost::Agent
|
|
111
|
+
description "Investigates support questions that need broader research."
|
|
112
|
+
model "customer_support.research"
|
|
113
|
+
system_prompt "Research the question and return a concise evidence summary."
|
|
114
|
+
end
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
There is no separate `customer_support.research` profile yet. Dotted roles fall back to the nearest registered parent, so this role resolves through `customer_support`. You can add a specialized profile later without changing `ResearchAgent`.
|
|
118
|
+
|
|
119
|
+
Now create `app/agents/customer_support_agent.rb`:
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
class CustomerSupportAgent < LittleGhost::Agent
|
|
123
|
+
description "Answers customer support questions."
|
|
124
|
+
model "customer_support"
|
|
125
|
+
system_prompt <<~PROMPT
|
|
126
|
+
Answer clearly and do not invent company guidance.
|
|
127
|
+
Use the help center lookup tool before stating company guidance.
|
|
128
|
+
Delegate open-ended investigation to the research agent.
|
|
129
|
+
PROMPT
|
|
130
|
+
|
|
131
|
+
tools HelpCenterLookupTool
|
|
132
|
+
subagent ResearchAgent, kind: "research"
|
|
133
|
+
end
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The class is a readable behavior boundary. It declares which model role, prompt, tools, and subagent capabilities can participate in a `CustomerSupportAgent` run. Declaring `ResearchAgent` as a subagent gives the model tools for delegation, with the turn, concurrency, and depth limits configured by the surrounding system; it does not force every request through research.
|
|
137
|
+
|
|
138
|
+
## Run the agent
|
|
139
|
+
|
|
140
|
+
From the application root, start a Ruby process that requires LittleGhost and asks the agent a question:
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
require "little_ghost"
|
|
144
|
+
|
|
145
|
+
runtime = LittleGhost::Runtime.new(configuration: LittleGhost.configuration)
|
|
146
|
+
customer_support_agent = CustomerSupportAgent.new(runtime:)
|
|
147
|
+
run = customer_support_agent.ask(
|
|
148
|
+
"I bought an item two weeks ago. Can I get a refund?"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
if run.completed?
|
|
152
|
+
puts run.response
|
|
153
|
+
else
|
|
154
|
+
warn "Support request ended as #{run.outcome}: #{run.error&.class}"
|
|
155
|
+
end
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`#ask` consumes the invocation and returns its owning `LittleGhost::Run`. A successful run exposes the final text through `#response` and the normalized terminal value through `#result`. It also retains outcome, usage, messages, and any terminal error.
|
|
159
|
+
|
|
160
|
+
The model can call `HelpCenterLookupTool` with `{"topic":"refunds"}` and answer along these lines:
|
|
161
|
+
|
|
162
|
+
```text
|
|
163
|
+
Refunds are available within 30 days, so a purchase from two weeks ago is eligible.
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Model wording is not deterministic. The important outcome is that the refund guidance comes from the validated tool result rather than from an unverified guess.
|
|
167
|
+
|
|
168
|
+
## Stream the response
|
|
169
|
+
|
|
170
|
+
Use `#stream_ask` when a console, web response, or UI should receive progress before the run completes:
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
CustomerSupportAgent.new(runtime:).stream_ask("Can I get a refund?").each do |event|
|
|
174
|
+
case event.type
|
|
175
|
+
when :text_delta
|
|
176
|
+
print event.data.fetch(:text)
|
|
177
|
+
when :run_error
|
|
178
|
+
warn event.data.fetch(:message)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The enumerator yields `LittleGhost::StreamEvent` objects. Text, tool activity, usage, traces, subagent activity, and terminal lifecycle facts share that interface, so adapters can translate one framework stream into a console, HTTP stream, or AG-UI transport.
|
|
184
|
+
|
|
185
|
+
## Where to go next
|
|
186
|
+
|
|
187
|
+
Read [Core Concepts](Core%20Concepts.md) for the distinction between model-directed subagents and application-directed workflows, how sessions and runs divide ownership, and when `ResponseWorkflow` is a better fit than delegation. The API reference covers exact signatures for `LittleGhost::Agent`, `LittleGhost::Tool`, `LittleGhost::ModelRegistry`, and `LittleGhost::Run`.
|