Documentation
¶
Overview ¶
Package claudeexecutor provides a generic executor for Claude-based agents that reduces boilerplate while maintaining flexibility for agent-specific logic.
The executor handles the common conversation loop pattern including:
- Prompt rendering from templates
- Message streaming and accumulation
- Tool call execution and response handling
- JSON response parsing
- Trace management for evaluation
Basic Usage ¶
Create an executor with a client and prompt template:
client := anthropic.NewClient(
vertex.WithGoogleAuth(ctx, region, projectID, "https://www.googleapis.com/auth/cloud-platform"),
)
tmpl, _ := template.New("prompt").Parse("Analyze: {{.Input}}")
exec, err := claudeexecutor.New[*Request, *Response](
client,
tmpl,
claudeexecutor.WithModel[*Request, *Response]("claude-3-opus@20240229"),
claudeexecutor.WithMaxTokens[*Request, *Response](16000),
)
if err != nil {
return nil, err
}
// Define tools if needed
tools := map[string]claudetool.Metadata[*Response]{
"read_file": {
Definition: anthropic.ToolParam{
Name: "read_file",
Description: anthropic.String("Read a file"),
InputSchema: anthropic.ToolInputSchemaParam{
Properties: map[string]interface{}{
"path": map[string]interface{}{
"type": "string",
"description": "File path",
},
},
Required: []string{"path"},
},
},
Handler: func(ctx context.Context, toolUse anthropic.ToolUseBlock, trace *agenttrace.Trace[*Response]) map[string]interface{} {
// Tool implementation
return map[string]interface{}{"content": "file contents"}
},
},
}
// Execute the agent
response, err := exec.Execute(ctx, request, tools)
Options ¶
The executor supports several configuration options:
- WithModel: Override the default model (defaults to claude-sonnet-4@20250514)
- WithMaxTokens: Set maximum response tokens (defaults to 8192, max 32000)
- WithTemperature: Set response temperature (defaults to 0.1)
- WithSystemInstructions: Provide system-level instructions
- WithThinking: Enable extended thinking mode with a token budget
- WithCacheFirstUserBlock: Also cache the first user message (off by default)
- WithMaxToolCallsBeforeFinalize: Soft-cap the agentic loop (off by default)
- WithForceSubmitToolChoice: Force the terminal submit tool via tool_choice (off by default)
Extended Thinking ¶
Extended thinking allows Claude to show its internal reasoning process before responding. When enabled, reasoning blocks are captured in the trace:
exec, err := claudeexecutor.New[*Request, *Response](
client,
prompt,
claudeexecutor.WithThinking[*Request, *Response](2048), // 2048 token budget for thinking
)
Reasoning blocks are stored in trace.Reasoning as []agenttrace.ReasoningContent, where each block contains:
- Thinking: the reasoning text
Note: When thinking is enabled, temperature is automatically set to 1.0 as required by the Claude API. See: https://docs.claude.com/en/docs/build-with-claude/extended-thinking
Claude Opus 4.7 Compatibility ¶
Opus 4.7 introduced two breaking changes that the executor handles transparently so callers don't need model-aware logic:
- Sampling parameters (temperature, top_p, top_k) are rejected with a 400. WithTemperature is silently dropped for Opus 4.7 models; a warning is logged once per Execute if the caller explicitly set it.
- Extended-thinking budgets are replaced by adaptive thinking. WithThinking(N) is mapped to adaptive thinking for Opus 4.7 (the budget is advisory to the model via adaptive mode). A warning is logged once per Execute noting the mapping. Display is set to "summarized" so reasoning traces remain populated.
See: https://platform.claude.com/docs/en/about-claude/models/whats-new-claude-4-7
Type Safety ¶
The executor is generic over Request and Response types, ensuring type safety throughout the conversation. The trace parameter in tool handlers is properly typed with the Response type.
Index ¶
- Constants
- type Interface
- type Option
- func WithCacheFirstUserBlock[Request promptbuilder.Bindable, Response any]() Option[Request, Response]
- func WithForceSubmitToolChoice[Request promptbuilder.Bindable, Response any](deferUntilToolName string) Option[Request, Response]
- func WithMaxTokens[Request promptbuilder.Bindable, Response any](tokens int64) Option[Request, Response]
- func WithMaxToolCallsBeforeFinalize[Request promptbuilder.Bindable, Response any](n int) Option[Request, Response]
- func WithMaxTurns[Request promptbuilder.Bindable, Response any](turns int) Option[Request, Response]
- func WithModel[Request promptbuilder.Bindable, Response any](model string) Option[Request, Response]
- func WithResourceLabels[Request promptbuilder.Bindable, Response any](labels map[string]string) Option[Request, Response]
- func WithRetryConfig[Request promptbuilder.Bindable, Response any](cfg retry.RetryConfig) Option[Request, Response]
- func WithSubmitResultProvider[Request promptbuilder.Bindable, Response any](provider SubmitResultProvider[Response]) Option[Request, Response]
- func WithSystemInstructions[Request promptbuilder.Bindable, Response any](prompt *promptbuilder.Prompt) Option[Request, Response]
- func WithTemperature[Request promptbuilder.Bindable, Response any](temp float64) Option[Request, Response]
- func WithThinking[Request promptbuilder.Bindable, Response any](budgetTokens int64) Option[Request, Response]
- func WithToolCallConcurrency[Request promptbuilder.Bindable, Response any](n int) Option[Request, Response]
- func WithoutCacheControl[Request promptbuilder.Bindable, Response any]() Option[Request, Response]
- type SubmitResultProvider
Examples ¶
Constants ¶
const DefaultMaxTurns = 200
DefaultMaxTurns is the default maximum number of conversation turns (LLM round-trips) before the executor aborts. Each turn corresponds to one Claude API call. This prevents runaway loops when the model keeps calling tools without converging on a result.
const DefaultToolCallConcurrency = 10
DefaultToolCallConcurrency is the default bound on how many of a single turn's tool calls run concurrently. Models routinely emit several independent tool calls in one turn (parallel tool use); dispatching their handlers concurrently cuts wall-clock latency. Override with WithToolCallConcurrency — a value of 1 restores strictly sequential dispatch.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Interface ¶
type Interface[Request promptbuilder.Bindable, Response any] interface { // Execute runs the agent conversation with the given request and tools // Optional seed tool calls can be provided - these will be executed and their results prepended to the conversation Execute(ctx context.Context, request Request, tools map[string]claudetool.Metadata[Response], seedToolCalls ...anthropic.ToolUseBlock) (Response, error) }
Interface is the public interface for Claude agent execution
type Option ¶
type Option[Request promptbuilder.Bindable, Response any] func(*executor[Request, Response]) error
Option is a functional option for configuring the executor
func WithCacheFirstUserBlock ¶ added in v0.7.2
func WithCacheFirstUserBlock[Request promptbuilder.Bindable, Response any]() Option[Request, Response]
WithCacheFirstUserBlock places an additional Anthropic cache breakpoint on the first user content block (the rendered prompt), in addition to the tool definitions and system prompt that are cached by default.
This is useful when the first user message carries a large payload (for example, per-request evidence embedded in the prompt) and the agent loop spans several turns: with the breakpoint, the API reads that payload from cache at 10% of the base input price on turns after the first, instead of re-billing it at full price each turn.
The breakpoint is only placed when prompt caching is enabled and a breakpoint slot remains within the API's limit, so it can never cause the API to reject a request for having too many breakpoints. Off by default.
Caching benefits accrue within a single model's iteration loop. A workflow that switches models mid-flight (for example, a cheap first pass that escalates to a stronger model) does not share a cached prefix across the switch, because the cache is keyed by the exact request prefix including the model. See: https://platform.claude.com/docs/en/build-with-claude/prompt-caching
func WithForceSubmitToolChoice ¶ added in v0.7.2
func WithForceSubmitToolChoice[Request promptbuilder.Bindable, Response any](deferUntilToolName string) Option[Request, Response]
WithForceSubmitToolChoice forces the model to call its terminal submit tool via tool_choice instead of leaving the choice to the model. This eliminates the wasted turn the executor would otherwise spend reactively redirecting a model that answered with plain text instead of calling the submit tool.
The force is applied on the first turn when deferUntilToolName is empty or names a tool that is NOT registered for the run. When deferUntilToolName names a tool that IS registered (for example a deferred-evidence fetch tool), the first turn stays at tool_choice auto so the model can gather that deferred evidence first; the submit tool is forced only on the turn after that gate tool has been called at least once.
The option is a no-op unless a terminal submit tool is configured via WithSubmitResultProvider — without one there is no tool to force toward. It is opt-in and off by default, so callers that do not set it keep the existing reactive behavior unchanged.
Not compatible with WithThinking: the API requires tool_choice auto/none while extended thinking is active and returns a 400 for a forced tool_choice, so construction fails when both are set. The order in which the two options are applied does not matter — the conflict is checked after all options are applied.
func WithMaxTokens ¶
func WithMaxTokens[Request promptbuilder.Bindable, Response any](tokens int64) Option[Request, Response]
WithMaxTokens sets the maximum tokens for responses
Example ¶
ExampleWithMaxTokens demonstrates configuring the maximum number of tokens the executor may generate per response.
package main
import (
"fmt"
"chainguard.dev/driftlessaf/agents/executor/claudeexecutor"
"chainguard.dev/driftlessaf/agents/promptbuilder"
)
func main() {
opt := claudeexecutor.WithMaxTokens[promptbuilder.Noop, *struct{}](16000)
fmt.Printf("option is nil: %v\n", opt == nil)
}
Output: option is nil: false
func WithMaxToolCallsBeforeFinalize ¶ added in v0.7.2
func WithMaxToolCallsBeforeFinalize[Request promptbuilder.Bindable, Response any](n int) Option[Request, Response]
WithMaxToolCallsBeforeFinalize bounds the agentic loop with a soft cap: once the model has made n investigative (non-terminal) tool calls, the executor injects a single instruction asking it to call its terminal submit tool now and forces that tool on the next turn.
This is distinct from WithMaxTurns, which aborts the run when exceeded. The soft cap instead steers the model toward emitting a result based on the evidence gathered so far, which is preferable for workflows that should always return a verdict rather than fail. A value of zero (the default) disables the nudge entirely. The nudge only takes effect when a terminal tool is configured via WithSubmitResultProvider; without one there is no tool to steer toward, so the option is a no-op.
Not compatible with WithThinking; the API requires tool_choice auto/none while thinking is active, and the forced tool_choice this option uses on the finalize turn returns a 400.
func WithMaxTurns ¶ added in v0.2.0
func WithMaxTurns[Request promptbuilder.Bindable, Response any](turns int) Option[Request, Response]
WithMaxTurns sets the maximum number of conversation turns (LLM round-trips) before the executor aborts. This prevents runaway loops where the model keeps calling tools without converging on a result. Default is DefaultMaxTurns (50).
func WithModel ¶
func WithModel[Request promptbuilder.Bindable, Response any](model string) Option[Request, Response]
WithModel allows overriding the model name
Example ¶
ExampleWithModel demonstrates configuring the Claude model used by the executor.
package main
import (
"fmt"
"chainguard.dev/driftlessaf/agents/executor/claudeexecutor"
"chainguard.dev/driftlessaf/agents/promptbuilder"
)
func main() {
opt := claudeexecutor.WithModel[promptbuilder.Noop, *struct{}]("claude-3-opus@20240229")
fmt.Printf("option is nil: %v\n", opt == nil)
}
Output: option is nil: false
func WithResourceLabels ¶
func WithResourceLabels[Request promptbuilder.Bindable, Response any](labels map[string]string) Option[Request, Response]
WithResourceLabels sets labels for GCP billing attribution when using Claude via Vertex AI. Automatically includes default labels from environment variables:
- service_name: from K_SERVICE, falling back to CLOUD_RUN_JOB (defaults to "unknown")
- product: from CHAINGUARD_PRODUCT (defaults to "unknown")
- team: from CHAINGUARD_TEAM (defaults to "unknown")
Custom labels passed to this function will override defaults if they use the same keys.
func WithRetryConfig ¶
func WithRetryConfig[Request promptbuilder.Bindable, Response any](cfg retry.RetryConfig) Option[Request, Response]
WithRetryConfig sets the retry configuration for handling transient Claude API errors. This is particularly useful for handling 429 rate limit and 529 overloaded errors. If not set, a default configuration is used.
func WithSubmitResultProvider ¶
func WithSubmitResultProvider[Request promptbuilder.Bindable, Response any](provider SubmitResultProvider[Response]) Option[Request, Response]
WithSubmitResultProvider registers the submit_result tool using the supplied provider. This is opt-in - agents must explicitly call this to enable submit_result.
func WithSystemInstructions ¶
func WithSystemInstructions[Request promptbuilder.Bindable, Response any](prompt *promptbuilder.Prompt) Option[Request, Response]
WithSystemInstructions sets custom system instructions
func WithTemperature ¶
func WithTemperature[Request promptbuilder.Bindable, Response any](temp float64) Option[Request, Response]
WithTemperature sets the temperature for responses Claude models support temperature values from 0.0 to 1.0 Lower values (e.g., 0.1) produce more deterministic outputs Higher values (e.g., 0.9) produce more creative/random outputs
func WithThinking ¶
func WithThinking[Request promptbuilder.Bindable, Response any](budgetTokens int64) Option[Request, Response]
WithThinking enables extended thinking mode with the specified token budget The budget_tokens parameter sets the maximum tokens Claude can use for reasoning This must be less than max_tokens and at least 1024 tokens is recommended
func WithToolCallConcurrency ¶ added in v0.7.10
func WithToolCallConcurrency[Request promptbuilder.Bindable, Response any](n int) Option[Request, Response]
WithToolCallConcurrency bounds how many of a single turn's tool calls run concurrently when the model emits more than one in a turn (parallel tool use). Defaults to DefaultToolCallConcurrency.
Results are always consumed in the order the model emitted them, so the tool_use/tool_result pairing the API requires is preserved, and the first terminal result (in order) ends the run.
A value of 1 runs the turn's tool calls strictly in order, one at a time. Set it to 1 for agents whose tool handlers mutate shared state (a worktree, a cache) without their own synchronization; concurrent dispatch is otherwise safe because handlers share only the trace, which is concurrency-safe.
func WithoutCacheControl ¶ added in v0.2.0
func WithoutCacheControl[Request promptbuilder.Bindable, Response any]() Option[Request, Response]
WithoutCacheControl disables Anthropic prompt caching.
Prompt caching is enabled by default because it significantly reduces input token costs for multi-turn agentic workflows. The API caches the request prefix (tool definitions + system prompt) and serves it at 10% of the normal input token price on subsequent turns. The only cost is a 1.25x write premium on the first turn, which is amortized across all subsequent cache reads within the 5-min TTL.
You would only disable this if you have a single-turn agent that runs less than once every 5 minutes, where the 1.25x write cost would never be recouped. See: https://platform.claude.com/docs/en/build-with-claude/prompt-caching
type SubmitResultProvider ¶
type SubmitResultProvider[Response any] func() (claudetool.Metadata[Response], error)
SubmitResultProvider constructs tool metadata for submit_result.