Skip to content

Commit 59fc27e

Browse files
authored
Merge pull request #13 from kofron/jared-agent-instructions
Agent-owned instructions (system) with streaming parity + docs and tests
2 parents 157a783 + 031539b commit 59fc27e

File tree

8 files changed

+730
-18
lines changed

8 files changed

+730
-18
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "tower-llm"
3-
version = "0.0.9"
3+
version = "0.0.10"
44
edition = "2021"
55
description = "A Tower-based framework for building LLM & agent workflows in Rust."
66
license = "MIT"

HANDOFF_GUIDE.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,29 @@ let observable_coordinator = tower_llm::observability::TracingLayer::new()
288288
3. **Prevent loops**: Include maximum handoff limits
289289
4. **Clear semantics**: Tool names should be self-explanatory
290290

291+
### Agent Instructions (System Prompt)
292+
293+
- Prefer setting per-agent instructions on the agent itself via `AgentBuilder::instructions(...)`.
294+
- At runtime, each agent injects its instructions as the first `system` message for its steps.
295+
- Handoff policies and the coordinator should not mutate `system` messages; keep that concern at the agent boundary.
296+
- Example:
297+
298+
```rust
299+
use std::sync::Arc;
300+
use async_openai::{config::OpenAIConfig, Client};
301+
use tower_llm::{Agent, run_user};
302+
303+
let client = Arc::new(Client::<OpenAIConfig>::new());
304+
let triage = Agent::builder(client.clone())
305+
.model("gpt-4o")
306+
.instructions("You are TRIAGE. Ask clarifying questions and route appropriately.")
307+
.build();
308+
let specialist = Agent::builder(client.clone())
309+
.model("gpt-4o")
310+
.instructions("You are SPECIALIST. Provide precise, technical answers.")
311+
.build();
312+
```
313+
291314
### Error Handling
292315

293316
1. **Validate handoffs**: Check target agents exist

README.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,120 @@ async fn main() -> anyhow::Result<()> {
6161
}
6262
```
6363

64+
## Architecture and rationale
65+
66+
At its core, tower-llm treats agents, tools, and coordinators as Tower services you compose with layers. This keeps business logic pure and testable while pushing side effects (I/O, retries, tracing, storage) to the edges.
67+
68+
- Agents are services that turn chat requests into outcomes.
69+
- Tools are services invoked by the agent when the model asks for them.
70+
- Layers add cross-cutting behavior without coupling concerns.
71+
72+
Why this design works well:
73+
74+
- Composable by default: you assemble exactly what you need.
75+
- Static dependency injection: no global registries or runtime lookups.
76+
- Functional core, imperative shell: easy to reason about and to test.
77+
78+
## A layered story: from hello world to production
79+
80+
Start small, add power as you go:
81+
82+
1. Hello world agent: see [Quickstart](#quickstart). One model, one tool, a simple stop policy.
83+
84+
2. Keep state between turns: add [Sessions](#sessions-stateful-agents) with `MemoryLayer` and an in-memory or SQLite store.
85+
86+
3. See what's happening: add [Observability](#observability-tracing-and-metrics) via `TracingLayer` and `MetricsLayer` to emit spans and metrics.
87+
88+
4. Bound cost and behavior: compose a [Budget policy](#budgeting-tokens-tools-time) with your stop policies.
89+
90+
5. Be resilient: wrap the agent with resilience layers (retry/timeout/circuit-breaker) from `resilience`.
91+
92+
6. Record and reproduce: tap runs with [Recording/Replay](#recording-and-replay) to debug or build fixtures without model calls.
93+
94+
7. Speed up tools: enable [Parallel tool execution](#parallel-tool-execution) and pick a join policy when multiple tools can run concurrently.
95+
96+
8. Stream in real time: use `streaming::StepStreamService` and `AgentLoopStreamLayer` for token-by-token UIs (see the streaming snippet below).
97+
98+
9. Go multi-agent: coordinate specialists with [Handoffs](#handoffs-multi-agent). Start with explicit or sequential policies; compose them as needed.
99+
100+
10. Keep context tight: add `auto_compaction::AutoCompactionLayer` or `groups::CompactingHandoffPolicy` to trim history during long runs (see `examples/handoff_compaction.rs`).
101+
102+
11. Validate conversations: use [Conversation validation](#conversation-validation-testsexamples) to assert invariants in tests and examples.
103+
104+
Throughout, you can swap providers or run entirely offline using [Provider override](#provider-override-no-network-testing).
105+
106+
## Layer catalog at a glance
107+
108+
- Sessions: persist and reload history around each call.
109+
110+
- When: you need stateful conversations or persistence.
111+
- How: `sessions::MemoryLayer`, with `InMemorySessionStore` or `SqliteSessionStore`.
112+
113+
- Observability: trace spans and emit metrics for every step/agent call.
114+
115+
- When: you want visibility in dev and prod.
116+
- How: `observability::{TracingLayer, MetricsLayer}` with your metrics sink.
117+
118+
- Approvals: gate model outputs and tool invocations.
119+
120+
- When: you need review or policy enforcement.
121+
- How: `approvals::{ModelApprovalLayer, ToolApprovalLayer}` plus an `Approver` service.
122+
123+
- Resilience: retry, time out, and break circuits around calls.
124+
125+
- When: you want robust error handling for flaky dependencies.
126+
- How: `resilience::{RetryLayer, TimeoutLayer, CircuitBreakerLayer}`.
127+
128+
- Recording/Replay: capture runs, replay deterministically without network.
129+
130+
- When: you want debuggable, repeatable scenarios or tests.
131+
- How: `recording::{RecorderLayer, ReplayService}` with a trace store.
132+
133+
- Budgets: constrain tokens, tools, or steps.
134+
135+
- When: you want cost and behavior bounds.
136+
- How: `budgets::budget_policy(...)`, composed in `CompositePolicy`.
137+
138+
- Concurrency: execute multiple tools concurrently; preserve output order.
139+
140+
- When: independent tools can run in parallel.
141+
- How: enable parallel tools on the builder or use `concurrency::ParallelToolRouter`; select a join policy.
142+
143+
- Streaming: emit tokens and tool events incrementally for UIs.
144+
145+
- When: you need real-time output.
146+
- How: `streaming::{StepStreamService, AgentLoopStreamLayer, StreamTapLayer}`.
147+
148+
- Handoffs (multi-agent): coordinate multiple agents at runtime.
149+
150+
- When: you have specialists or workflows.
151+
- How: `groups::{AgentPicker, HandoffPolicy, HandoffCoordinator, GroupBuilder}`; see also `CompactingHandoffPolicy`.
152+
153+
- Auto compaction: trim conversation history safely.
154+
155+
- When: you approach token limits or want faster iterations.
156+
- How: `auto_compaction::{AutoCompactionLayer, CompactionPolicy}`; or wrap handoffs with `CompactingHandoffPolicy`.
157+
158+
- Provider override: swap the model implementation.
159+
- When: offline tests or custom backends.
160+
- How: `provider::{FixedProvider, SequenceProvider, OpenAIProvider}` with the `ModelService` trait.
161+
162+
## Agent-level instructions (system)
163+
164+
Attach the system prompt at the agent level so it applies consistently across steps and handoffs.
165+
166+
```rust
167+
let mut agent = Agent::builder(client)
168+
.model("gpt-4o")
169+
.instructions("You are a helpful assistant.")
170+
.tool(echo)
171+
.policy(policies::max_steps(1).into())
172+
.build();
173+
174+
// Send only a user message; the agent injects its instructions as a system message
175+
let _ = tower_llm::run_user(&mut agent, "Say hi").await?;
176+
```
177+
64178
## Sessions (stateful agents)
65179

66180
Attach `MemoryLayer` at build time using store services that implement `Service<LoadSession>` and `Service<SaveSession>`.

0 commit comments

Comments
 (0)