agent

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 4, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package agent implements the M2 query loop — the streaming agentic turn cycle that drives tool dispatch and multi-turn conversation.

The loop mirrors src/query.ts's queryLoop() but with M5 additions: permission gate checks and PreToolUse/PostToolUse hook runners around each tool execution.

Loop behaviour:

  1. POST /v1/messages with current conversation history.
  2. Stream SSE events; collect text deltas and tool_use blocks.
  3. If the stop_reason is "tool_use": a. Check permissions gate for each tool. b. Run PreToolUse hooks. c. Execute each tool in sequence (serial; concurrency in M4). d. Run PostToolUse hooks. e. Append assistant message + user tool_result message to history. f. Go to 1 (unless MaxTurns exceeded).
  4. If stop_reason is "end_turn": return.

Package agent assembles the request body for /v1/messages.

The request body shape — system blocks, tools, metadata — is part of how Anthropic identifies legitimate Claude Code clients (alongside headers and OAuth scopes). A bare `{model, messages, max_tokens}` body is rate- limited as a non-CLI caller even with all the right headers. We replicate the captured shape from real Claude Code 2.1.126 (mitmproxy 2026-05-01, see /tmp/conduit-capture/real_body.json).

Index

Constants

View Source
const BillingHeader = "x-anthropic-billing-header: cc_version=2.1.126.824; cc_entrypoint=sdk-cli; cch=0f7c5;\n"

BillingHeader is the `cc_version=…; cc_entrypoint=…; cch=…` line the real CLI puts as the first system block. The `cch` value appears to be a per-build checksum; we ship the value captured from 2.1.126 verbatim and allow it to be overridden at runtime via CLAUDE_GO_BILLING_HEADER for experimentation. If Anthropic rotates the secret, we'll need to update this constant alongside Version.

View Source
const MinimalAgentSystemPrompt = `` /* 1439-byte string literal not displayed */

MinimalAgentSystemPrompt is the third system block: the main agent prompt. Trimmed copy of the real CC system prompt — long enough to look like the real thing but short enough that we don't ship Anthropic's full IP.

Real prompt is ~10 KB; ours is intentionally minimal but structurally similar. The API checks total system byte count + cache_control shape more than exact wording.

View Source
const MinimalIdentitySystem = "You are a Claude agent, built on Anthropic's Claude Agent SDK."

MinimalIdentitySystem is the second system block: the agent identity declaration. Empirically required to keep the request shape "CC-shaped".

View Source
const MinimalOutputGuidance = `` /* 444-byte string literal not displayed */

MinimalOutputGuidance is the fourth system block. Empirically the API also accepts shorter — we keep this terse so the M1 binary works without shipping Anthropic's full prompt verbatim.

Variables

View Source
var CoordinatorMCPNames []string

CoordinatorMCPNames can be set at startup to make BuildSystemBlocks inject the connected MCP server names into the coordinator worker-context block. Should be populated from the MCPManager before the first request.

Functions

func BuildMetadata

func BuildMetadata(deviceID, accountUUID, sessionID string) map[string]any

BuildMetadata mirrors the metadata block the real CLI sends, with device/account/session identifiers. We use the supplied values and stamp a unique session id.

func BuildSystemBlocks

func BuildSystemBlocks(memory, claudeMd string, skills ...SkillEntry) []api.SystemBlock

BuildSystemBlocks returns the system field that mimics the real CLI's request shape. Caller can override BillingHeader via the CLAUDE_GO_BILLING_HEADER env var.

Block layout:

  1. Billing header
  2. Identity
  3. Agent system prompt (cache_control: ephemeral, scope: global)
  4. Output guidance (cache_control: ephemeral)
  5. [optional] Coordinator system prompt + worker-tools context (when coordinator mode active)
  6. [optional] CLAUDE.md instructions (claudeMd != "")
  7. [optional] Full memory prompt from memdir.BuildPrompt (memory != "") Includes type taxonomy, how-to-save instructions, and MEMORY.md content.
  8. [optional] Skills reminder (skills non-empty)

func SkillsReminder

func SkillsReminder(skills []SkillEntry) string

SkillsReminder is the system-reminder block injected when skills are available. Mirrors the real CC's dynamic system block listing available slash-command skills. Format: "# Available skills\n\n- name: description\n- name2: description2" The model uses this listing to decide when to proactively call SkillTool.

Types

type EventType

type EventType int

EventType identifies what kind of loop event the caller receives.

const (
	EventText       EventType = iota // a text delta streamed from the model
	EventToolUse                     // a tool_use block completed; tool is about to run
	EventToolResult                  // tool execution finished
	EventRateLimit                   // rate-limit headers received; RateLimitWarning may be non-empty
	EventPartial                     // stream errored mid-turn; PartialBlocks holds what was received
)

type Loop

type Loop struct {
	// contains filtered or unexported fields
}

Loop drives the agentic query cycle.

func NewLoop

func NewLoop(client *api.Client, reg *tool.Registry, cfg LoopConfig) *Loop

NewLoop constructs a Loop.

func (*Loop) GetThinkingBudget

func (l *Loop) GetThinkingBudget() int

GetThinkingBudget returns the current thinking budget.

func (*Loop) Run

func (l *Loop) Run(ctx context.Context, messages []api.Message, handler func(LoopEvent)) ([]api.Message, error)

Run executes the agentic loop starting with the given messages. handler is called synchronously for each event; it must not block.

Returns the full accumulated message history (including all tool turns) and nil error on clean end_turn. On error, returns whatever history was built before the failure. Callers should replace their history slice with the returned messages to correctly track multi-turn tool use.

func (*Loop) RunSubAgent

func (l *Loop) RunSubAgent(ctx context.Context, prompt string) (string, error)

RunSubAgent runs a nested agent loop with the given prompt as the sole user message. Used by AgentTool and SkillTool to spawn forked sub-agents. The sub-agent inherits the same tools, model, and system prompt but starts with a fresh single-turn history. Returns the concatenated text from the final assistant message.

When coordinator mode is active, the result is wrapped in a <task-notification> XML block so the coordinator model can identify and process it correctly per its system prompt instructions.

func (*Loop) SetAskPermission

func (l *Loop) SetAskPermission(fn func(ctx context.Context, toolName, toolInput string) (allow, alwaysAllow bool))

SetAskPermission installs the interactive permission callback. Called from the TUI after the Bubble Tea program is created.

func (*Loop) SetClient

func (l *Loop) SetClient(client *api.Client)

SetClient swaps the API client (e.g. after a fresh login reloads credentials).

func (*Loop) SetModel

func (l *Loop) SetModel(name string)

SetModel updates the model used for new requests (from /model slash command).

func (*Loop) SetSystem

func (l *Loop) SetSystem(blocks []api.SystemBlock)

SetSystem replaces the system blocks for subsequent requests.

func (*Loop) SetThinkingBudget

func (l *Loop) SetThinkingBudget(budget int)

SetThinkingBudget updates the thinking budget for subsequent requests. Set to 0 to disable thinking. Used by /effort command.

type LoopConfig

type LoopConfig struct {
	Model     string
	MaxTokens int
	System    []api.SystemBlock
	Metadata  map[string]any
	// MaxTurns caps the number of API calls (tool-use follow-ups each count
	// as one turn). 0 means no limit (use carefully).
	MaxTurns int
	// Cwd is the working directory for the session. Used for persisting
	// "always allow" rules to <cwd>/.claude/settings.local.json.
	Cwd string

	// Gate is the permission gate to consult before each tool call.
	// nil means no gate (all tools allowed).
	Gate *permissions.Gate

	// Hooks is the hooks configuration to run around tool calls.
	// nil means no hooks.
	Hooks *settings.HooksSettings

	// SessionID is used when invoking hooks (passed as session_id in hook input).
	SessionID string

	// AutoCompact enables automatic history compaction when input token usage
	// exceeds 80% of MaxTokens. Mirrors the auto-compact behavior in
	// src/services/compact/compact.ts and QueryEngine.ts.
	AutoCompact bool

	// ThinkingBudget, when > 0, sends thinking:{type:"enabled",budget_tokens:N}
	// in each API request. Requires the interleaved-thinking-2025-05-14 beta header.
	// Set via /effort command or CLAUDE_THINKING_BUDGET env var.
	ThinkingBudget int

	// NotifyOnComplete, when true, fires a desktop notification after each
	// end_turn (not after tool-use turns). Mirrors the notifs hook behavior.
	NotifyOnComplete bool

	// AskPermission is called when a tool needs interactive approval.
	// It blocks until the user responds. Returns (allow, alwaysAllow).
	// nil means DecisionAsk → allow through silently.
	AskPermission func(ctx context.Context, toolName, toolInput string) (allow, alwaysAllow bool)

	// OnFileAccess is called after each file tool execution with the operation
	// ("read" or "write") and the file path. Used to populate /files output.
	OnFileAccess func(op, path string)

	// OnEndTurn fires after each end_turn (no tool_use) with the up-to-date
	// message history. Mirrors CC's post-Stop extractMemories trigger. The
	// caller is expected to single-flight any background work — Loop fires
	// this synchronously before returning so the caller can choose between
	// blocking or detaching to a goroutine.
	OnEndTurn func(history []api.Message)

	// OnCompact fires when auto-compaction runs and provides the summary text.
	// Used by the TUI to persist the summary to the session transcript.
	OnCompact func(summary string)

	// MicroCompact, when true, runs time-based microcompaction before each
	// request (mirrors src/services/compact/microCompact.ts time-based path).
	// When the gap since the last assistant message exceeds MicroCompactGap,
	// older tool_results are replaced with a placeholder. The cache is
	// expired anyway past that gap, so this shrinks what gets re-cached
	// without changing functional context.
	MicroCompact     bool
	MicroCompactGap  time.Duration // default 60m if zero
	MicroCompactKeep int           // default 5 if zero
	// LastAssistantTime seeds the gap calculation on resume. If zero, the
	// loop initializes from the first assistant response.
	LastAssistantTime time.Time
}

LoopConfig controls the loop's behaviour.

type LoopEvent

type LoopEvent struct {
	Type EventType

	// EventText
	Text string

	// EventToolUse
	ToolName  string
	ToolID    string
	ToolInput json.RawMessage

	// EventToolResult
	ResultText string
	IsError    bool

	// EventRateLimit
	RateLimitWarning string // non-empty when quota is running low
	RateLimitInfo    ratelimit.Info

	// EventPartial — fired before a stream error bubbles up so callers
	// can persist whatever assistant content was streamed before the
	// failure. The blocks here are already filtered (empty text/truncated
	// tool_use dropped by buildContentBlocks).
	PartialBlocks []api.ContentBlock
	PartialErr    error
}

LoopEvent is emitted to the caller's handler on each significant event.

type SkillEntry

type SkillEntry struct {
	Name        string
	Description string
}

SkillEntry is one item in the skills listing.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL