Skip to main content
All options are passed to chainforge.NewAgent(...) as functional options.

Provider & Model

OptionTypeDescription
WithProvider(p)core.ProviderRequired. LLM provider implementation.
WithModel(model)stringRequired. Model identifier (e.g. "claude-sonnet-4-6").

Provider shortcuts

These one-call shortcuts set both provider and model atomically:
ShorthandEquivalent
WithAnthropic(apiKey, model)WithProvider(anthropic.New(apiKey)) + WithModel(model)
WithOpenAI(apiKey, model)WithProvider(openai.New(apiKey)) + WithModel(model)
WithGemini(apiKey, model)WithProvider(gemini.New(apiKey, model)) + WithModel(model)
WithOllama(baseURL, model)WithProvider(ollama.New(baseURL)) + WithModel(model)
WithOpenAICompatible(apiKey, baseURL, name, model)WithProvider(openai.NewWithBaseURL(...)) + WithModel(model)
agent, err := chainforge.NewAgent(
    chainforge.WithAnthropic(os.Getenv("ANTHROPIC_API_KEY"), "claude-sonnet-4-6"),
    chainforge.WithSystemPrompt("You are a helpful assistant."),
)
Later options override earlier ones, so WithAnthropic followed by WithModel("other") uses "other".

Config file

Load provider configuration from a YAML file:
agent, err := chainforge.FromConfigFile("config.yaml",
    chainforge.WithSystemPrompt("You are a helpful assistant."),
    chainforge.WithTools(myTools...),
)
Minimal config.yaml:
provider: anthropic   # anthropic | openai | ollama
api_key: sk-ant-...   # or set ANTHROPIC_API_KEY env var
model: claude-sonnet-4-6
FromConfigFile wraps all errors with "chainforge: FromConfigFile: ..." for easy identification.

Behaviour

OptionDefaultDescription
WithMaxIterations(n)10Maximum agent loop iterations before returning ErrMaxIterations.
WithToolTimeout(d)30sPer-tool execution timeout. Errors are non-fatal and fed back to the LLM.
WithRunTimeout(d)0 (none)Per-run deadline applied to the entire Run/RunWithUsage call. Returns context.DeadlineExceeded if the loop does not finish in time. 0 means no timeout.
WithMaxTokens(n)4096Max tokens per LLM call.
WithTemperature(f)0.7Sampling temperature.
WithMaxHistory(n)0 (unlimited)Caps the number of history messages loaded from memory per Run call to the most recent n. Prevents context window overflow on long sessions. Has no effect when no memory store is attached.
WithRetry(n)Wraps the provider with automatic retry on transient errors. n is the total number of attempts (1 = no retry, 3 = 2 retries). Uses exponential backoff: 200 ms → 400 ms → 800 ms … capped at 10 s. Context cancellation and deadline errors are never retried.
WithStreamBufferSize(n)16Sets the RunStream channel buffer capacity. Increase for high-throughput streaming with long tool chains to reduce back-pressure.
WithToolConcurrency(n)0 (unlimited)Caps the number of tool goroutines that run simultaneously during one dispatchTools call. Useful when tools make external API calls and you want to avoid bursting 50+ concurrent requests. 0 restores the unlimited default.

Prompt & Tools

OptionDescription
WithSystemPrompt(s)System message prepended to every conversation.
WithTools(tools...)Register one or more tools. Can be called multiple times.
WithMemory(m)Attach a memory store for cross-run history persistence. Built-in stores: inmemory, sqlite, postgres, redis, qdrant. See the Memory guide.
WithStructuredOutput(schema)Validate every final LLM response against a JSON schema (json.RawMessage). Returns ErrInvalidOutput if the response is not valid JSON or its top-level type doesn’t match. Also injects a system prompt hint. See the Structured Output guide.
WithHistorySummarizer(a)When history exceeds WithMaxHistory, compress the overflow into a single [Summary: ...] message using agent a instead of dropping old messages. Requires WithMaxHistory > 0. The summarizer runs under "<sessionID>:summarizer". Errors from the summarizer propagate immediately to the caller. See the Memory guide.

RAG (Retrieval-Augmented Generation)

OptionDescription
WithRetriever(r, opts...)Enable automatic RAG. Before the first iteration, the retriever is queried with the user message and the top-K documents are appended to the system prompt. See the RAG guide.
opts is ...rag.RetrieveOption. Currently supported: rag.WithTopK(n int) — number of documents to retrieve (default: 5).
retriever := rag.NewQdrantRetriever(store, embedder, "kb")
agent, _ := chainforge.NewAgent(
    chainforge.WithAnthropic(key, "claude-sonnet-4-6"),
    chainforge.WithRetriever(retriever, rag.WithTopK(5)),
)

HITL (Human-in-the-Loop)

OptionDescription
WithHITLGateway(g)Gate every tool call through a hitl.Gateway before execution. Rejected calls receive an override message instead of running. See the HITL guide.
agent, _ := chainforge.NewAgent(
    chainforge.WithProvider(p),
    chainforge.WithHITLGateway(hitl.NewCLIGateway(os.Stdout, os.Stdin)),
)

MCP

OptionDescription
WithMCPServer(s)Register a single MCP server. Connection is deferred to the first Run call.
WithMCPServers(servers...)Register multiple MCP servers at once.

Observability

OptionDefaultDescription
WithLogger(l)slog.Default()Structured slog.Logger for the agent loop.
WithLogging(logger)Wraps the provider with slog middleware. Logs every Chat/ChatStream call with latency and token counts. Falls back to slog.Default() if logger is nil.
WithTracing()Wraps the provider with OpenTelemetry spans. Session ID is automatically added to every span. Uses the global tracer — call otel.InitTracerProvider first. No-op if the global tracer is uninitialised.
WithTraceAttributes(fn)Appends extra OTel span attributes to every Chat/ChatStream call. fn receives the call context so it can extract request-scoped values (user ID, tenant, etc.). Requires WithTracing().
WithDebugHandler(fn)Fires fn synchronously at each step of the agent loop: before/after every LLM call and before/after every tool execution. Use PrettyPrintDebugHandler(os.Stderr) for instant local tracing. Intended for development — use WithLogging/WithTracing for production.
// Instant human-readable transcript to stderr
agent, _ := chainforge.NewAgent(
    chainforge.WithProvider(p),
    chainforge.WithModel("claude-sonnet-4-6"),
    chainforge.WithDebugHandler(chainforge.PrettyPrintDebugHandler(os.Stderr)),
)
Output:
[iter 0] → LLM  (3 messages)
[iter 0] ← LLM  stop=tool_use  ""
[iter 0] ⚙  tool=search  input={"query":"Go concurrency"}
[iter 0] ✓  tool=search  result=Go uses goroutines and channels…
[iter 1] → LLM  (5 messages)
[iter 1] ← LLM  stop=end_turn  "Go concurrency is based on goroutines…"

ProviderBuilder

ProviderBuilder is an explicit, ordered alternative to the WithLogging / WithRetry / WithTracing options. Use it when you need fine-grained control over wrapper ordering, or want to share a pre-built provider across multiple agents.
p := chainforge.NewProviderBuilder(anthropic.New(apiKey)).
    WithRetry(3).                                     // retry first (innermost)
    WithRateLimit(10, 20).                            // 10 rps, burst 20
    WithFallback(openai.New(os.Getenv("OPENAI_API_KEY"))). // fallback on error
    WithMetrics(prometheus.DefaultRegisterer).        // Prometheus metrics
    WithLogging(logger).                              // log every call
    WithTracing().                                    // OTel spans (outermost)
    Build()

agent, _ := chainforge.NewAgent(
    chainforge.WithProvider(p),
    chainforge.WithModel("claude-sonnet-4-6"),
)
MethodDescription
NewProviderBuilder(base)Start building from a base provider
.WithRetry(maxAttempts)Add exponential-backoff retry
.WithRateLimit(rps, burst)Add token-bucket rate limiting; blocks until a token is available or context is cancelled
.WithFallback(fallbacks...)Add fallback chain; tries each provider in order on error
.WithMetrics(reg)Add Prometheus metrics (requests, latency, tokens)
.WithLogging(logger)Add slog logging (nil → slog.Default())
.WithTracing()Add OpenTelemetry spans
.Build()Return the composed provider
Build is idempotent — wrappers are applied in the order they were registered.

RunWithUsage

Run discards token counts. Use RunWithUsage to receive the total core.Usage accumulated across all iterations of the agent loop:
result, usage, err := agent.RunWithUsage(ctx, "session-1", "Hello")
fmt.Printf("input=%d output=%d\n", usage.InputTokens, usage.OutputTokens)

RunStreamCollect

A streaming one-liner that accumulates the full text and calls onDelta for each chunk. Useful when you want real-time display but also need the final string:
text, usage, err := agent.RunStreamCollect(ctx, "session-1", "Hello",
    func(delta string) { fmt.Print(delta) })
Pass nil for onDelta to get usage without real-time display:
text, usage, err := agent.RunStreamCollect(ctx, "session-1", "Hello", nil)

RunStream (raw)

RunStream emits usage on the final Done event:
for ev := range agent.RunStream(ctx, "session-1", "Hello") {
    if ev.Type == core.StreamEventDone && ev.Usage != nil {
        fmt.Printf("tokens: %d in / %d out\n", ev.Usage.InputTokens, ev.Usage.OutputTokens)
    }
}
Both WithLogging and WithTracing are applied after all other options are resolved, so their position relative to WithProvider does not matter. They can be stacked:
chainforge.NewAgent(
    chainforge.WithProvider(p),
    chainforge.WithModel("claude-sonnet-4-6"),
    chainforge.WithLogging(logger),  // wraps provider first
    chainforge.WithTracing(),        // wraps the logged provider
)

Errors

ErrorCondition
ErrMaxIterationsAgent loop reached maxIterations without a stop reason.
ErrToolNotFoundLLM called a tool name that isn’t registered. Non-fatal — returned as tool result.
ErrNoProviderNewAgent called without WithProvider.
ErrNoModelNewAgent called without WithModel.
ErrInvalidOutputWithStructuredOutput is set and the final LLM response fails JSON/schema validation.
provider init errorWithGemini (or other provider shortcuts) failed to create the provider — now surfaces the original error at NewAgent time instead of returning ErrNoProvider.
misconfigurationWithHistorySummarizer without WithMaxHistory returns an error at NewAgent time.
tool name invalidTool with an empty name, non-alphanumeric/underscore/hyphen characters, or a duplicate name returns an error at NewAgent time.

Error constructors

Use the typed constructors to build structured errors:
// ToolError — wraps a tool execution failure
err := core.NewToolError("my_tool", originalErr)
// Error() → `tool "my_tool": <originalErr>`

// ProviderError — wraps a provider failure with optional HTTP status
err := core.NewProviderError("anthropic", 429, originalErr)
// Error() → `provider "anthropic" (status 429): <originalErr>`

// Zero status code omits the status segment:
err := core.NewProviderError("openai", 0, originalErr)
// Error() → `provider "openai": <originalErr>`
Both implement Unwrap() so errors.Is / errors.As work transparently.