Skip to content

@providerprotocol/agents

@providerprotocol/agents


A powerful, flexible agent framework implementing the Unified Agent Protocol (UAP) 1.0. Built on top of @providerprotocol/ai for seamless multi-provider LLM support.

  • Functional State Management - Immutable state with explicit data flow
  • Execution Strategies - Choose from loop, react, or plan patterns
  • Middleware Pipeline - Composable before/after hooks for logging, guardrails, etc.
  • Multi-Provider Support - Works with Anthropic, OpenAI, Google, Ollama, and more
  • Streaming - Full streaming support with UAP and UPP events
  • Checkpointing - Built-in session persistence and recovery
  • Thread Trees - Branching conversation support
  • Sub-Agent Tools - Helper utilities for creating tools that spawn sub-agents with event propagation
  • Tool Context Injection - Pass execution context (agentId, stateId, toolCallId) to tools
  • Tool Ordering - Execute tool calls respecting dependencies and sequential barriers
  • Type-Safe - 100% TypeScript with full type inference
Terminal window
bun install @providerprotocol/agents @providerprotocol/ai
import { agent, AgentState } from '@providerprotocol/agents';
import { anthropic } from '@providerprotocol/ai/anthropic';
// Create an agent
const assistant = agent({
model: anthropic('claude-sonnet-4-20250514'),
params: { max_tokens: 4096 },
system: 'You are a helpful assistant.',
});
// Single query (stateless)
const turn = await assistant.query('What is the capital of France?');
console.log(turn.response.text);
// Multi-turn conversation (stateful)
let state = AgentState.initial();
const r1 = await assistant.ask('My name is Alice.', state);
state = r1.state;
const r2 = await assistant.ask('What is my name?', state);
console.log(r2.turn.response.text); // "Alice"
import { agent, AgentState } from '@providerprotocol/agents';
import { anthropic } from '@providerprotocol/ai/anthropic';
const calculator = {
name: 'calculate',
description: 'Evaluate a math expression',
parameters: {
type: 'object' as const,
properties: {
expression: { type: 'string' as const },
},
required: ['expression'],
},
run: async (params: { expression: string }) => {
// Use a proper math parser in production
return String(eval(params.expression));
},
};
const mathBot = agent({
model: anthropic('claude-sonnet-4-20250514'),
params: { max_tokens: 1000 },
tools: [calculator],
});
const turn = await mathBot.query('What is 42 * 17?');
console.log(turn.response.text);
console.log(turn.toolExecutions); // Shows calculator was used

Choose how your agent thinks and acts:

Simple tool loop - keeps executing until no more tool calls.

import { loop } from '@providerprotocol/agents/execution';
const myAgent = agent({
model: anthropic('claude-sonnet-4-20250514'),
execution: loop({ maxIterations: 10 }),
});

Reason-Act-Observe pattern with explicit reasoning phase.

import { react } from '@providerprotocol/agents/execution';
const myAgent = agent({
model: anthropic('claude-sonnet-4-20250514'),
execution: react({ maxSteps: 20 }),
});

Generate a structured plan, then execute steps with dependencies.

import { plan } from '@providerprotocol/agents/execution';
const myAgent = agent({
model: anthropic('claude-sonnet-4-20250514'),
execution: plan({ maxPlanSteps: 10, allowReplan: true }),
});
const stream = myAgent.stream('Explain quantum computing', state);
for await (const event of stream) {
if (event.source === 'upp' && event.upp?.type === 'text_delta') {
process.stdout.write(event.upp.delta.text ?? '');
}
}
const { turn, state: newState } = await stream.result;
import { logging } from '@providerprotocol/agents/middleware';
const myAgent = agent({
model: anthropic('claude-sonnet-4-20250514'),
middleware: [
logging({ level: 'info', includeTiming: true }),
],
});

Create custom middleware:

import type { Middleware } from '@providerprotocol/agents/middleware';
const rateLimiter: Middleware = {
name: 'rate-limiter',
async before(context) {
await checkRateLimit(context.agent.id);
return context;
},
async after(context, result) {
recordUsage(result.turn.usage);
return result;
},
};

Persist agent sessions for recovery:

import { fileCheckpoints } from '@providerprotocol/agents/checkpoint';
const store = fileCheckpoints({ dir: '.checkpoints' });
const myAgent = agent({
model: anthropic('claude-sonnet-4-20250514'),
checkpoints: store,
sessionId: 'my-session',
});
// Execute - state automatically saved after each step
await myAgent.generate('Hello', AgentState.initial());
// Later: restore and continue
const saved = await store.load('my-session');
if (saved) {
const state = AgentState.fromJSON(saved);
await myAgent.generate('Continue...', state);
}
const myAgent = agent({
model: anthropic('claude-sonnet-4-20250514'),
strategy: {
stopCondition: (state) => state.step > 50,
onStepStart: (step, state) => console.log(`Step ${step}`),
onAct: (step, toolCalls) => console.log('Tools:', toolCalls),
onComplete: (result) => console.log('Done!'),
onError: (error, state) => console.error(error),
},
});

Works with all providers from @providerprotocol/ai:

import { anthropic } from '@providerprotocol/ai/anthropic';
import { openai } from '@providerprotocol/ai/openai';
import { google } from '@providerprotocol/ai/google';
import { ollama } from '@providerprotocol/ai/ollama';
// Anthropic
agent({ model: anthropic('claude-sonnet-4-20250514'), params: { max_tokens: 4096 } });
// OpenAI
agent({ model: openai('gpt-4o'), params: { max_output_tokens: 4096 } });
// Google
agent({ model: google('gemini-2.0-flash'), params: { maxOutputTokens: 4096 } });
// Ollama (local)
agent({ model: ollama('llama3:8b'), params: { num_predict: 4096 } });

Create a .env file with your API keys:

ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...
GOOGLE_API_KEY=AI...

Creates an agent instance.

OptionTypeDescription
modelModelReferenceRequired. Model from UPP provider
paramsobjectLLM parameters (provider-specific)
systemstringSystem prompt
toolsTool[]Available tools
executionExecutionStrategyStrategy: loop(), react(), plan()
middlewareMiddleware[]Middleware pipeline
strategyAgentStrategyLifecycle hooks
checkpointsCheckpointStoreSession persistence
sessionIdstringSession identifier
MethodReturnsDescription
generate(input, state)Promise<GenerateResult>Execute with state
stream(input, state)AgentStreamResultStream execution
ask(input, state)Promise<GenerateResult>Multi-turn convenience
query(input)Promise<Turn>Stateless single-turn

Immutable state with chainable operations:

state.withMessage(msg) // Add message
state.withMessages(msgs) // Add messages
state.withContext(msgs) // Replace all messages
state.withStep(n) // Set step number
state.withMetadata(k, v) // Add metadata
state.withReasoning(text) // Add reasoning trace
state.withPlan(steps) // Set execution plan
state.toJSON() // Serialize
AgentState.fromJSON(json) // Deserialize

See the examples/ directory for complete examples:

  • coding-agent - A Claude Code-style coding assistant with file operations, bash, search, and sub-agents

Run the coding agent example:

Terminal window
ANTHROPIC_API_KEY=sk-... bun examples/coding-agent/index.ts "List all TypeScript files"

Interactive mode:

Terminal window
ANTHROPIC_API_KEY=sk-... bun examples/coding-agent/index.ts -i

TUI mode:

Terminal window
ANTHROPIC_API_KEY=sk-... bun examples/coding-agent/index.ts --tui
Terminal window
bun test # All tests
bun test:unit # Unit tests
bun test:live # Live API tests
bun lint # Lint
bun typecheck # Type check

“UAP is a pipe, not a nanny.”

This framework provides orchestration primitives with sensible defaults but no artificial limits. By default:

  • Execution continues until naturally complete (no max iterations)
  • State is explicit and immutable
  • All UPP types flow through without abstraction
  • You control safety, budgets, and constraints
  • llms.md - Comprehensive API documentation for LLMs and developers
  • CLAUDE.md - Project coding guidelines and @providerprotocol/ai usage

MIT