Documentation
¶
Overview ¶
Package headless provides a subprocess-based interface for running claude -p headlessly. It manages session pools for prefix-cache reuse, streaming output, and clean subprocess lifecycle management.
Index ¶
- Constants
- Variables
- func AllowedFeatureKeyList() string
- func DraftPRDescription(ctx context.Context, pool *Pool, diff, branchName string) (string, error)
- func GenerateAcceptanceCriteria(ctx context.Context, pool *Pool, title, description string) ([]string, error)
- func HeadlessReviewSystemPrompt() string
- func HeadlessTriageSystemPrompt() string
- func ReviewSystemPrompt() string
- func SetDefaultPool(p *Pool)
- func SuggestCommitMessage(ctx context.Context, pool *Pool, diff string) (string, error)
- func SummarizeBacklogItem(ctx context.Context, pool *Pool, title, description string) (string, error)
- type CallOptions
- type ClaudeRunner
- type FakeRunner
- func (f *FakeRunner) ArgsContainSequence(n int, seq ...string) bool
- func (f *FakeRunner) ArgsForCall(n int) []string
- func (f *FakeRunner) CallCount() int
- func (f *FakeRunner) HasArg(arg string) bool
- func (f *FakeRunner) Run(_ context.Context, args []string, _ io.Reader) (io.ReadCloser, func() error, error)
- func (f *FakeRunner) SetErrors(errs ...error)
- type FeatureKey
- type Pool
- func (p *Pool) Call(ctx context.Context, key FeatureKey, systemPrompt, userPrompt string) (<-chan StreamChunk, error)
- func (p *Pool) CallBlocking(ctx context.Context, key FeatureKey, systemPrompt, userPrompt string) (string, error)
- func (p *Pool) CallBlockingWithOptions(ctx context.Context, key FeatureKey, systemPrompt, userPrompt string, ...) (string, error)
- func (p *Pool) CallWithOptions(ctx context.Context, key FeatureKey, systemPrompt, userPrompt string, ...) (<-chan StreamChunk, error)
- type PoolClient
- type PoolConfig
- type ProcessRunner
- type StreamChunk
Constants ¶
const DefaultCallTimeout = 900 * time.Second
DefaultCallTimeout is the default headless call timeout applied when timeout_seconds is 0.
const MaxCallTimeout = 1800 * time.Second
MaxCallTimeout caps timeout_seconds.
const MaxDiffSizeReview = 40_000
MaxDiffSizeReview is the maximum number of bytes included in a review prompt diff.
Variables ¶
var ( // ErrClaudeNotFound is returned when the claude binary is not in PATH. ErrClaudeNotFound = errors.New("claude binary not found in PATH") // ErrLLMError is returned when claude exits with code 1 (LLM-level error). ErrLLMError = errors.New("claude LLM error (exit 1)") // ErrUsageError is returned when claude exits with code 2 (bad usage / bad flags). ErrUsageError = errors.New("claude usage error (exit 2)") // ErrInterrupted is returned when claude exits with code 130 (SIGINT). ErrInterrupted = errors.New("claude interrupted (exit 130)") )
Error sentinels returned in StreamChunk.Err or from CallBlocking.
var AllowedFeatureKeys = map[FeatureKey]bool{ FeatureKeyReview: true, FeatureKeySummarize: true, FeatureKeyAC: true, FeatureKeyPRDescription: true, FeatureKeyCommitMessage: true, FeatureKeyCustom: true, }
AllowedFeatureKeys is the set of feature keys accepted by the MCP-exposed RunHeadlessCall path (server/services/headless_service.go). FeatureKeyTriage is intentionally excluded — triage calls go through BacklogService.TriggerTriage → Pool.CallBlockingWithOptions directly, bypassing the MCP gate. This prevents triage from being triggered via the public headless API.
Functions ¶
func AllowedFeatureKeyList ¶
func AllowedFeatureKeyList() string
AllowedFeatureKeyList returns a sorted comma-separated list of allowed feature keys for use in error messages. Generated from AllowedFeatureKeys to stay in sync.
func DraftPRDescription ¶
DraftPRDescription calls the LLM to draft a pull request description. Diffs longer than maxDiffSizePR bytes are truncated before sending.
func GenerateAcceptanceCriteria ¶
func GenerateAcceptanceCriteria(ctx context.Context, pool *Pool, title, description string) ([]string, error)
GenerateAcceptanceCriteria calls the LLM to generate acceptance criteria. Returns a slice of criterion strings.
func HeadlessReviewSystemPrompt ¶
func HeadlessReviewSystemPrompt() string
HeadlessReviewSystemPrompt returns the system prompt for headless (no-tool) review calls. Requests JSON output so the caller can parse the verdict without tool execution.
func HeadlessTriageSystemPrompt ¶
func HeadlessTriageSystemPrompt() string
HeadlessTriageSystemPrompt returns the stable system prompt for headless triage calls. Requests JSON output so the caller can parse the result without MCP tool execution.
func ReviewSystemPrompt ¶
func ReviewSystemPrompt() string
ReviewSystemPrompt returns the stable system prompt for review gate calls. Exported so session/backlog_lifecycle.go can use it without embedding the prompt inline.
func SetDefaultPool ¶
func SetDefaultPool(p *Pool)
SetDefaultPool sets the package-level default pool. Safe to call concurrently.
func SuggestCommitMessage ¶
SuggestCommitMessage calls the LLM to generate a Conventional Commit message. Diffs longer than maxDiffSizeCommit bytes are truncated before sending.
Types ¶
type CallOptions ¶
type CallOptions struct {
// WorkDir sets the subprocess working directory (for git operations).
WorkDir string
// Model overrides the pool's DefaultModel for this call only.
Model string
// TimeoutSecs is unused by Pool directly — callers wrap ctx with WithTimeout.
TimeoutSecs int
}
CallOptions configures an individual pool call with overrides.
type ClaudeRunner ¶
type ClaudeRunner interface {
// Run starts claude -p with the given args. stdin provides the user prompt so
// it does not appear in /proc/<pid>/cmdline. Returns a ReadCloser for stdout,
// a stop function to kill the process, and an error if the process fails to start.
// The caller must call stop() to release resources even when the ReadCloser is drained.
Run(ctx context.Context, args []string, stdin io.Reader) (stdout io.ReadCloser, stop func() error, err error)
}
ClaudeRunner abstracts how claude -p subprocesses are started. Implementors: ProcessRunner (real), FakeRunner (tests).
type FakeRunner ¶
type FakeRunner struct {
// Calls records every set of args passed to Run, in order.
Calls [][]string
// contains filtered or unexported fields
}
FakeRunner is a test double for ClaudeRunner. It returns scripted responses and records call arguments for inspection.
When the args contain "--output-format" followed by "json", the response must be valid JSON matching firstCallJSONResult schema:
{"session_id":"...","result":"...","cost_usd":0.0}
Otherwise the response is returned as plain text, line by line.
func NewFakeRunner ¶
func NewFakeRunner(responses ...string) *FakeRunner
NewFakeRunner creates a FakeRunner that returns responses in order. If responses is empty the runner returns an empty string for each call.
func (*FakeRunner) ArgsContainSequence ¶
func (f *FakeRunner) ArgsContainSequence(n int, seq ...string) bool
ArgsContainSequence returns true if the nth call's args contain the given sequence.
func (*FakeRunner) ArgsForCall ¶
func (f *FakeRunner) ArgsForCall(n int) []string
ArgsForCall returns the args recorded for the nth call (0-indexed). Returns nil if call n has not happened yet.
func (*FakeRunner) CallCount ¶
func (f *FakeRunner) CallCount() int
CallCount returns how many times Run has been called.
func (*FakeRunner) HasArg ¶
func (f *FakeRunner) HasArg(arg string) bool
HasArg returns true if any recorded call contains arg.
func (*FakeRunner) Run ¶
func (f *FakeRunner) Run(_ context.Context, args []string, _ io.Reader) (io.ReadCloser, func() error, error)
Run returns the next scripted response (or error). It records args in Calls. stdin is accepted to satisfy the ClaudeRunner interface but is not inspected. The stop function is a no-op.
func (*FakeRunner) SetErrors ¶
func (f *FakeRunner) SetErrors(errs ...error)
SetErrors configures per-call errors. A nil entry means no error for that call.
type FeatureKey ¶
type FeatureKey string
FeatureKey is a named type for feature identifiers. Using a named type (not an alias) prevents accidental string injection at call sites.
const ( FeatureKeyReview FeatureKey = "review" FeatureKeySummarize FeatureKey = "summarize" FeatureKeyAC FeatureKey = "acceptance-criteria" FeatureKeyPRDescription FeatureKey = "pr-description" FeatureKeyCommitMessage FeatureKey = "commit-message" FeatureKeyCustom FeatureKey = "custom" FeatureKeyAutonomousFix FeatureKey = "autonomous_fix" FeatureKeyAutonomousApproval FeatureKey = "autonomous_approval" FeatureKeyTriage FeatureKey = "triage" )
Feature key constants for well-known AI features.
type Pool ¶
type Pool struct {
// contains filtered or unexported fields
}
Pool manages a map of named LLM feature sessions, providing session reuse for prefix-cache optimization and bounded concurrency.
func DefaultPool ¶
func DefaultPool() *Pool
DefaultPool returns the package-level default pool. Returns nil if SetDefaultPool has not been called.
func NewPool ¶
func NewPool(cfg PoolConfig) (*Pool, error)
NewPool constructs a Pool by looking up the claude binary in PATH, falling back to well-known install locations if PATH lookup fails. Returns ErrClaudeNotFound if the binary is not found anywhere.
func NewPoolWithRunner ¶
func NewPoolWithRunner(cfg PoolConfig, runner ClaudeRunner) *Pool
NewPoolWithRunner constructs a Pool with a custom runner (no PATH lookup). Used in tests to inject a FakeRunner.
func (*Pool) Call ¶
func (p *Pool) Call(ctx context.Context, key FeatureKey, systemPrompt, userPrompt string) (<-chan StreamChunk, error)
Call starts a streaming headless LLM call for the given feature key. It returns a channel that receives StreamChunk values. The channel is closed when the subprocess exits (or the context is cancelled).
The caller should drain the channel until Done=true or Err!=nil.
func (*Pool) CallBlocking ¶
func (p *Pool) CallBlocking(ctx context.Context, key FeatureKey, systemPrompt, userPrompt string) (string, error)
CallBlocking runs a headless LLM call and blocks until the result is complete. Returns the concatenated text from all chunks and the first non-nil error.
func (*Pool) CallBlockingWithOptions ¶
func (p *Pool) CallBlockingWithOptions(ctx context.Context, key FeatureKey, systemPrompt, userPrompt string, opts CallOptions) (string, error)
CallBlockingWithOptions is like CallBlocking but supports WorkDir and Model overrides.
func (*Pool) CallWithOptions ¶
func (p *Pool) CallWithOptions(ctx context.Context, key FeatureKey, systemPrompt, userPrompt string, opts CallOptions) (<-chan StreamChunk, error)
CallWithOptions is like Call but allows overriding model and working directory.
When opts.WorkDir is non-empty a fresh one-shot subprocess is used (bypassing session caching, which is invalid across directory changes). The parent pool's concurrency semaphore is still acquired so WorkDir calls count against the pool-level cap.
When opts.WorkDir is empty, opts.Model is forwarded to the pool's acquireSession so the correct model is used for the first-call (session-initialisation) request.
type PoolClient ¶
type PoolClient interface {
CallBlockingWithOptions(ctx context.Context, key FeatureKey, systemPrompt, userPrompt string, opts CallOptions) (string, error)
}
PoolClient is the narrow interface BacklogService uses for headless triage calls. Satisfied by *Pool; allows test injection without needing FakeRunner WorkDir support.
type PoolConfig ¶
type PoolConfig struct {
// MaxCallsPerSession is the maximum number of calls before a session is rotated.
// Defaults to 25 if zero.
MaxCallsPerSession int
// MaxConcurrentSessions is the maximum number of concurrent subprocess calls.
// Defaults to 5 if zero.
MaxConcurrentSessions int
// DefaultModel overrides the claude model used when no model is specified per-call.
DefaultModel string
}
PoolConfig configures a Pool.
type ProcessRunner ¶
type ProcessRunner struct {
// contains filtered or unexported fields
}
ProcessRunner implements ClaudeRunner using executor.StartProcess.
func (*ProcessRunner) Run ¶
func (r *ProcessRunner) Run(ctx context.Context, args []string, stdin io.Reader) (io.ReadCloser, func() error, error)
Run starts the claude binary with args and returns a ReadCloser for stdout. stdin provides the user prompt to the subprocess so it does not appear in /proc/<pid>/cmdline. The stop function terminates the subprocess and must always be called.
func (*ProcessRunner) WithWorkDir ¶
func (r *ProcessRunner) WithWorkDir(workDir string) *ProcessRunner
WithWorkDir returns a copy of this ProcessRunner that sets the subprocess working directory to workDir. Used by CallBlockingWithOptions for per-call directory override.