bridle

package module
v0.1.3 Latest Latest
Warning

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

Go to latest
Published: May 23, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

README

bridle

CI Release Go Reference License

The harness layer of the Nexus funnel/harness split. A small Go library that drives one deliberation turn of one model with one tool surface, emitting a stream of observable events and returning a structured TurnResult.

The funnel imports bridle. Aspects do not import bridle directly.

A bridle controls and directs without being the horse — sits beneath the funnel, governs what the model produces, provider-agnostic.

Status

Spec drafted, no implementation yet. See docs/2026-05-01-bridle-spec.md for the build brief.

Scope

  • One stable provider interface, N implementations: claude-api, ollama-local, openai-api.
  • register_hooks-style interception surface for §5.6 behaviors.
  • send_comms is just a tool the funnel supplies — bridle has no special case.
  • Funnel owns session JSONL; bridle proposes deltas.

Non-goals

  • Not a framework, not an agent runtime, not a session store. It does one thing: drive one turn.
  • See spec §9 for the full non-goals list.

Reference reading

PydanticAI (iter() / CallToolsNode), Eino (streaming + tool-call normalization), PI (headless JSON streaming), Strands (register_hooks), LangGraph (interrupt/checkpoint).

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrModelRequired = errors.New("bridle: TurnRequest.Model is required")

ErrModelRequired is returned by RunTurn when TurnRequest.Model is empty.

View Source
var ErrToolNameCollision = errors.New("bridle: tool name collision between explicit Tools and MCP-loaded tools")

ErrToolNameCollision is returned by RunTurn when a tool name appears in both TurnRequest.Tools (explicit) and the MCP-loaded tool surface.

Functions

func AppendAssistantText added in v0.1.3

func AppendAssistantText(finalText *string, sessionDelta *[]SessionEvent, providerID ProviderID, text string)

AppendAssistantText accumulates a chunk of model-emitted text into *finalText and appends a matching assistant-role SessionEvent (with Provider set) to *sessionDelta. Does NOT emit a ModelChunk — that's the caller's job when the chunk is observed live during a streaming loop.

Centralises the trio (accumulate + SessionEvent shape) that every direct-API provider's extractResult / subprocess-stream provider's text branch repeats. Concretely, this is the spot where forgetting to set Provider silently broke ParseSessionEvent in three providers; using this helper makes the field impossible to omit.

func EmitAssistantText added in v0.1.3

func EmitAssistantText(sink EventSink, finalText *string, sessionDelta *[]SessionEvent, providerID ProviderID, text string)

EmitAssistantText is AppendAssistantText plus a live ModelChunk emit. Use from subprocess-stream provider parsers (claudecode, geminicli) where the same text is BOTH streamed live AND folded into the final result/session log. Direct-API providers that emit chunks inside their SDK stream loop and lower a separate aggregate in extractResult should use AppendAssistantText in the lowering path and call sink.Emit directly in the stream loop.

func IsProviderErrorKind added in v0.1.1

func IsProviderErrorKind(err error, kind ProviderErrorKind) bool

IsProviderErrorKind reports whether err (or any error in its chain) is a ProviderError with the given kind.

Types

type AfterToolCallCtx

type AfterToolCallCtx struct {
	Call   ToolCall
	Result ToolCallResult
	Step   int
}

AfterToolCallCtx carries context passed to AfterToolCall hooks.

type BeforeModelCallCtx

type BeforeModelCallCtx struct {
	Request *ProviderRequest
	Step    int
}

BeforeModelCallCtx carries context passed to BeforeModelCall hooks.

Request points at the live ProviderRequest that the harness is about to send to the provider — the same struct, not a copy. Hooks may mutate its fields in place (Model, AppendSystemPrompt, Tools, ProviderEnv, Messages, etc.) and the changes apply to the upcoming call. The hook fires once before the initial call (Step=0) and once before every subsequent call inside the tool loop (Step=N), so per-step mutations (escalate model on N, drop a tool once used) work.

Mutating Messages is supported but advanced: by the time the in-loop hook fires, the harness has already appended the assistant tool_use turn and the tool_result blocks for the round just completed.

type BeforeToolCallCtx

type BeforeToolCallCtx struct {
	Call ToolCall
	Step int
}

BeforeToolCallCtx carries context passed to BeforeToolCall hooks.

type Event

type Event interface {
	// contains filtered or unexported methods
}

Event is the union type for all observable harness events.

type EventSink

type EventSink interface {
	Emit(Event)
}

EventSink receives events as the turn unfolds.

type Harness

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

Harness drives one deliberation turn with one provider.

func NewHarness

func NewHarness(p Provider) *Harness

NewHarness creates a Harness backed by the given provider.

func (*Harness) RegisterAfterToolCall

func (h *Harness) RegisterAfterToolCall(fn Hook[AfterToolCallCtx]) HookID

RegisterAfterToolCall adds a hook that fires after each tool execution. Returns a HookID that can be passed to UnregisterHook.

func (*Harness) RegisterBeforeModelCall

func (h *Harness) RegisterBeforeModelCall(fn Hook[BeforeModelCallCtx]) HookID

RegisterBeforeModelCall adds a hook that fires before each model invocation. Returns a HookID that can be passed to UnregisterHook.

func (*Harness) RegisterBeforeToolCall

func (h *Harness) RegisterBeforeToolCall(fn Hook[BeforeToolCallCtx]) HookID

RegisterBeforeToolCall adds a hook that fires before each tool execution. Returns a HookID that can be passed to UnregisterHook.

func (*Harness) RegisterOnStepBoundary

func (h *Harness) RegisterOnStepBoundary(fn Hook[OnStepBoundaryCtx]) HookID

RegisterOnStepBoundary adds a hook that fires between tool-call rounds. Returns a HookID that can be passed to UnregisterHook.

func (*Harness) RegisterOnTurnDone

func (h *Harness) RegisterOnTurnDone(fn Hook[OnTurnDoneCtx]) HookID

RegisterOnTurnDone adds a hook that fires after the turn completes. Hooks may mutate TurnResult.SessionDelta. Returns a HookID that can be passed to UnregisterHook.

func (*Harness) RunTurn

func (h *Harness) RunTurn(ctx context.Context, req TurnRequest, runner ToolRunner, sink EventSink) (result TurnResult, err error)

RunTurn drives one turn: calls the provider, executes tool calls via runner, fires hooks at documented points, and emits events to sink. Cancellation via ctx returns a partial TurnResult with StopReason=aborted. Returns ErrModelRequired if req.Model is empty.

func (*Harness) UnregisterHook added in v0.1.3

func (h *Harness) UnregisterHook(id HookID) bool

UnregisterHook removes the hook with the given id from whichever hook slice it was registered into. Returns true if a hook was removed, false if no hook with that id exists. The zero HookID is never registered and always returns false.

Not safe to call concurrently with RunTurn or with Register*. See HookID for the threading contract.

type Hook

type Hook[T any] func(ctx context.Context, in T) (T, HookAction, error)

Hook is the generic hook signature. T is the mutable context value passed in and returned. Registration order is the execution order.

type HookAction

type HookAction int

HookAction tells the harness what to do after a hook returns.

const (
	HookContinue HookAction = iota
	HookAbort               // end the turn; partial TurnResult returned with StopReason=aborted
)

type HookID added in v0.1.3

type HookID uint64

HookID identifies a registered hook so it can be removed via Harness.UnregisterHook. The zero value is not a valid hook id — every successful Register* call returns a non-zero id.

Hook registration is NOT safe to call concurrently with RunTurn or with itself; mirror the existing assumption that hooks are wired during setup. If you need to swap a hook between turns, do it from a single goroutine while no turn is in flight.

type InboxItem

type InboxItem struct {
	From    string
	Content string
	MsgID   int64
	RawJSON json.RawMessage

	// ThreadRoot is the canonical thread identity for the message
	// (linked-list root id; nexus task #226). The funnel uses it to
	// key per-thread session state so each thread gets its own
	// claude-code jsonl, preventing SessionTail bleed across threads.
	// Zero = legacy/non-chat synthetic item or pre-#226 row.
	ThreadRoot int64

	// Source identifies which trigger channel produced this item.
	// Empty defaults to "chat" (legacy / nexus-chat substrate).
	// agora-side callers set Source="tty" for operator-typed inputs,
	// allowing ReturnHandlers to branch routing (chat → bus reply,
	// tty → panel-only). Future trigger channels add new Source
	// values; consumers default-treat unknown values as "chat" for
	// backward compat.
	Source string
}

InboxItem is a comms message that arrived during the previous turn. The harness folds these into the prompt context before the first model call. Read-only from the harness's perspective.

MsgID is the chat msg_id this item was sourced from. It carries through into the prompt so the model can reference items by id when triaging ("triage(msg_id=17, decision='reply')"). Zero means the item didn't originate from a chat message — it's an internal/synthetic item the funnel injected, and the triage contract doesn't apply.

type MCPClientConfig

type MCPClientConfig struct {
	Servers []MCPServerSpec
}

MCPClientConfig describes how bridle connects to MCP servers and what tool surface the model sees from them. The funnel constructs this; bridle consumes it. Ignored by subprocess-stream providers (SupportsMCP=false).

type MCPServerSpec

type MCPServerSpec struct {
	Name      string            // local identifier, used in tool-call provenance
	Transport MCPTransport      // stdio | http_sse
	Command   []string          // argv to spawn the server (stdio only)
	URL       string            // server URL (http_sse only)
	Env       map[string]string // environment variables for spawned server (stdio only)
	Header    map[string]string // request headers (http_sse only)
}

MCPServerSpec describes a single MCP server connection.

type MCPTransport

type MCPTransport string

MCPTransport identifies the wire transport for an MCP server connection.

const (
	MCPTransportStdio   MCPTransport = "stdio"
	MCPTransportHTTPSSE MCPTransport = "http_sse"
)

type ModelChunk

type ModelChunk struct {
	Text string
}

ModelChunk carries a streamed text fragment from the model.

type NormalizedSessionEvent

type NormalizedSessionEvent struct {
	Role    SessionRole
	Content string // human-readable text representation
}

NormalizedSessionEvent is a provider-agnostic view of a SessionEvent, used when displaying or processing session history without knowing the provider's wire format.

func ParseSessionEvent

func ParseSessionEvent(e SessionEvent) (NormalizedSessionEvent, error)

ParseSessionEvent returns a normalized view of a session event. For events with RawJSON, it attempts a provider-specific parse; falls back to Content if RawJSON is absent or unrecognized.

type OnStepBoundaryCtx

type OnStepBoundaryCtx struct {
	Step int
}

OnStepBoundaryCtx carries context passed to OnStepBoundary hooks.

type OnTurnDoneCtx

type OnTurnDoneCtx struct {
	Result *TurnResult
}

OnTurnDoneCtx carries context passed to OnTurnDone hooks. Hooks may mutate SessionDelta before it is returned to the funnel.

type Provider

type Provider interface {
	Name() ProviderID
	Capabilities() ProviderCapabilities
	RunTurn(ctx context.Context, req ProviderRequest, sink EventSink) (ProviderResult, error)
}

Provider is the interface every model backend must implement. Provider-specific weirdness (streaming, wire format, tool-schema translation) stays inside the implementation; the harness sees a uniform event stream.

type ProviderCapabilities

type ProviderCapabilities struct {
	Category               ProviderCategory
	SupportsCustomTools    bool // funnel can pass arbitrary Tools via TurnRequest
	SupportsBeforeToolCall bool // BeforeToolCall hook fires
	SupportsAfterToolCall  bool // AfterToolCall hook fires
	SupportsMCP            bool // provider consumes TurnRequest.MCP (direct-api only)
}

ProviderCapabilities advertises what a provider supports so the harness and funnel can route turns correctly.

type ProviderCategory

type ProviderCategory string

ProviderCategory classifies how a provider executes tool calls.

const (
	// CategoryDirectAPI — provider talks directly to a model API; bridle owns the tool loop.
	CategoryDirectAPI ProviderCategory = "direct-api"
	// CategorySubprocessStream — provider spawns a subprocess that runs its own agentic loop
	// and emits a structured event stream. The subprocess owns tool execution.
	CategorySubprocessStream ProviderCategory = "subprocess-stream"
)

type ProviderError added in v0.1.1

type ProviderError struct {
	Kind    ProviderErrorKind
	Message string
	Err     error // underlying error (may be nil)
}

ProviderError is a classified provider-level error.

func (*ProviderError) Error added in v0.1.1

func (e *ProviderError) Error() string

func (*ProviderError) Unwrap added in v0.1.1

func (e *ProviderError) Unwrap() error

type ProviderErrorKind added in v0.1.1

type ProviderErrorKind string

ProviderErrorKind classifies a provider-level error so callers can surface a distinct diagnosis string instead of an opaque exit code.

const (
	ProviderErrorAuthFailed   ProviderErrorKind = "auth_failed"
	ProviderErrorRateLimit    ProviderErrorKind = "rate_limit"
	ProviderErrorServerError  ProviderErrorKind = "server_error"
	ProviderErrorNetworkError ProviderErrorKind = "network_error"
	ProviderErrorTimeout      ProviderErrorKind = "timeout"
	ProviderErrorTLSError     ProviderErrorKind = "tls_error"
	// ProviderErrorSubprocessExit is the fallback kind used when a
	// subprocess-style provider exited non-zero and no other
	// classification matched. Callers can filter for this via
	// IsProviderErrorKind to handle the generic-failure case
	// distinctly from the more specific classes above.
	ProviderErrorSubprocessExit ProviderErrorKind = "subprocess_exit"
)

type ProviderID

type ProviderID string

ProviderID identifies a model provider.

const (
	ProviderClaude     ProviderID = "claude-api"
	ProviderClaudeCode ProviderID = "claude-code"
	ProviderClaudePty  ProviderID = "claude-pty"
	ProviderOllama     ProviderID = "ollama-local"
	ProviderOpenAI     ProviderID = "openai-api"
	ProviderBedrock    ProviderID = "bedrock"
	ProviderGemini     ProviderID = "gemini-api"
	ProviderGeminiCLI  ProviderID = "gemini-cli"
)

type ProviderMessage

type ProviderMessage struct {
	Role       string // "user" | "assistant" | "tool_result" | "system"
	Content    string
	ToolCallID string           // links a tool_result back to the call that produced it
	ToolName   string           // function-declaration name; required for tool_result on Gemini
	ToolCalls  []ToolInvocation // tool_use blocks for assistant turns; nil on other roles
}

ProviderMessage is a single exchange entry in provider-agnostic form.

For Role == "tool_result", both ToolCallID and ToolName must be set. ToolCallID is the call instance identifier the assistant emitted (used to correlate this result with that specific invocation). ToolName is the function-declaration name that was called (e.g. "send_chat") — some providers (Gemini's FunctionResponse) require it to be present alongside the call id, because their wire format keys responses by declaration name, not by call id. Providers that key only by call id (Anthropic, OpenAI, Ollama) ignore ToolName and the field can be left empty without harm.

For Role == "assistant", ToolCalls carries the structured tool_use blocks the model emitted on this turn. Providers that send assistant history back to the model (claude, openai, gemini, bedrock) MUST reconstruct these as native tool_use blocks; sending only Content as plain text loses the tool-call structure and breaks multi-turn tool conversations on strict providers (Bedrock rejects, Anthropic and OpenAI are lenient but degrade). Content and ToolCalls can both be non-empty — text and tool_use blocks coexist in one assistant turn.

type ProviderRequest

type ProviderRequest struct {
	AspectID           string
	AppendSystemPrompt string
	Session            SessionHandle // for subprocess-stream: resume key; for direct-api: may be empty
	Messages           []ProviderMessage
	Tools              []ToolDef
	ToolChoice         string           // see TurnRequest.ToolChoice
	MCP                *MCPClientConfig // nil = no MCP tools
	MaxSteps           int
	Model              string

	// Cwd is the working directory for subprocess-style providers (see
	// TurnRequest.Cwd). Empty falls through to bridle's host cwd. Direct-
	// API providers ignore this field.
	Cwd string

	// ProviderEnv is per-turn auth/routing env (see TurnRequest.ProviderEnv).
	// Subprocess providers overlay it onto the spawned process's env;
	// direct-API providers read it as auth/base-url config. Empty/nil =
	// provider uses its own default config.
	ProviderEnv map[string]string
}

ProviderRequest is the harness-internal lowered form of TurnRequest. System prompt is assembled, session tail is flattened to the provider's message format, tools are translated to provider-specific schema, and inbox items are folded in.

type ProviderResult

type ProviderResult struct {
	FinalText    string
	ToolCalls    []ToolInvocation
	StepCount    int
	Usage        Usage
	StopReason   StopReason
	SessionDelta []SessionEvent
	// ResolvedModel is the model id the upstream API actually returned
	// (e.g. "claude-3-5-sonnet-20241022"). May differ from
	// ProviderRequest.Model when per-turn ProviderEnv routed the call
	// elsewhere. Empty when the provider doesn't surface a model id.
	// Flows into TurnResult.ResolvedModel.
	ResolvedModel string
}

ProviderResult is the harness-internal result from one provider turn step.

FinalText is the model's settled assistant text for this round — what downstream consumers (e.g. nexus funnel auto-post to chat) should treat as "what the model said." The harness concatenates FinalText across rounds for direct-API providers (see run.go), because each round is a separate, intentional deliberation.

Subprocess-stream providers that parse multi-event streams must decide what counts as "settled" before populating FinalText. Some models (Claude trained for claudecode) produce a draft → tool → final answer pattern within one subprocess run; the draft is exploratory and should NOT survive into FinalText, or auto-post emits a doubled "draft + rewrite" row (operator chat #951, harrow #944). claudecode handles this by resetting accumulated text on every tool_use, so FinalText ends up containing only post-last-tool text.

Other subprocess-stream providers (geminicli) don't currently apply the same heuristic — that's an explicit per-model judgement, not a missed fix. Revisit if a model exhibits the draft-rewrite pattern without this policy.

type SessionEvent

type SessionEvent struct {
	Provider ProviderID  `json:"provider,omitempty"` // who produced this event
	Role     SessionRole `json:"role"`
	Content  string      `json:"content,omitempty"`
	// RawJSON carries provider-specific blocks (tool_use, tool_result, etc.)
	// that don't fit the plain content field. Valid only in conjunction with Provider.
	RawJSON json.RawMessage `json:"raw,omitempty"`
}

SessionEvent is a single entry in a session's event log. The harness consumes SessionTail on the way in and proposes SessionDelta on the way out.

type SessionHandle

type SessionHandle struct {
	ID  string // opaque to the funnel; meaningful to the provider
	New bool   // true on the first invocation for this ID; false on continuations
}

SessionHandle is an opaque reference to provider-side session state. The funnel mints handles and maps them to threads; the provider uses the ID to resume state (e.g., --resume <session-id> for subprocess-stream). For direct-api providers, Handle may be empty; state comes from SessionTail.

New tells the provider whether the funnel is initiating a fresh session for this ID (true) or asking it to continue an existing one (false). For subprocess-stream providers like claudecode that maintain their own jsonl files, this is the difference between "create with this id" vs "load existing id". Direct-api providers that derive state from SessionTail can ignore this field.

type SessionRole

type SessionRole string

SessionRole identifies who produced a session event.

const (
	RoleUser      SessionRole = "user"
	RoleAssistant SessionRole = "assistant"
	RoleTool      SessionRole = "tool"
	RoleSystem    SessionRole = "system"
)

type StepBoundary

type StepBoundary struct {
	Step int
}

StepBoundary fires between tool-call rounds. Step 1 = the first round; fires after its results are sent back to the model.

type StopReason

type StopReason string

StopReason explains why a turn ended.

const (
	StopReasonModelDone StopReason = "model_done"
	StopReasonMaxSteps  StopReason = "max_steps"
	StopReasonError     StopReason = "error"
	StopReasonAborted   StopReason = "aborted"
	// StopReasonProcessExit is set when the underlying provider process
	// exited non-zero AFTER producing parseable assistant content. The
	// returned ProviderResult carries whatever the model said before
	// the exit — callers should treat the result as truncated-but-real,
	// not discard it. Common cause: hitting an output-token cap and the
	// CLI surfacing that as a non-zero exit rather than a clean stop.
	StopReasonProcessExit StopReason = "process_exit"
)

type ToolCall

type ToolCall struct {
	ID   string
	Name string
	Args json.RawMessage
}

ToolCall is a single invocation the model requested.

type ToolCallResult

type ToolCallResult struct {
	ID     string
	Result json.RawMessage
	Err    string // non-empty if the tool runner returned an error
}

ToolCallResult fires after the tool runner returns (or errors).

type ToolCallStart

type ToolCallStart struct {
	ID   string
	Name string
	Args json.RawMessage
}

ToolCallStart fires when the model requests a tool call, before execution.

type ToolDef

type ToolDef struct {
	Name        string
	Description string
	// InputSchema is a JSON Schema object describing the expected arguments.
	InputSchema json.RawMessage
}

ToolDef describes a tool the model may call.

type ToolInvocation

type ToolInvocation struct {
	ID     string
	Name   string
	Args   json.RawMessage
	Result json.RawMessage
	Err    string
}

ToolInvocation records a single tool call the model made.

type ToolRunner

type ToolRunner interface {
	Run(ctx context.Context, call ToolCall) (json.RawMessage, error)
}

ToolRunner executes tool calls on behalf of the harness. The funnel supplies the implementation; the harness never owns tools.

type TurnDone

type TurnDone struct {
	Result TurnResult
}

TurnDone fires after the turn completes successfully.

type TurnError

type TurnError struct {
	Err   error
	Stage TurnErrorStage
}

TurnError fires when the provider or harness hits a non-recoverable error. Never panics across the harness boundary.

Stage labels the pipeline location where the error surfaced — useful for log routing and dashboards. See TurnErrorStage for the enumerated values bridle emits; consumers MAY observe other strings (forwarded from wire, set by tests). Free-form is intentional.

type TurnErrorStage added in v0.1.3

type TurnErrorStage string

TurnErrorStage names a pipeline location that produced a TurnError. The underlying type is a string so consumers that just log it (or receive forwarded values from the wire) continue to work.

const (
	// TurnErrorStageHarnessRecover — panic trap inside Harness.RunTurn.
	TurnErrorStageHarnessRecover TurnErrorStage = "harness-recover"
	// TurnErrorStageProvider — provider.RunTurn returned a non-nil
	// error before producing a complete result.
	TurnErrorStageProvider TurnErrorStage = "provider"
	// TurnErrorStageRetry — a transient provider error is being
	// retried (claudecode); informational, not terminal.
	TurnErrorStageRetry TurnErrorStage = "retry"
	// TurnErrorStageProviderAPIError — claudecode stream-json reported
	// is_api_error=true; the run continues but the kind is surfaced.
	TurnErrorStageProviderAPIError TurnErrorStage = "provider_api_error"
	// TurnErrorStageSubprocessExit — a subprocess-stream provider
	// exited non-zero and the classifier had no better label.
	TurnErrorStageSubprocessExit TurnErrorStage = "subprocess_exit"
	// TurnErrorStageSubprocessExitPartial — subprocess exited non-zero
	// AFTER producing parseable assistant content; the partial result
	// is preserved with StopReason=process_exit.
	TurnErrorStageSubprocessExitPartial TurnErrorStage = "subprocess_exit_partial"
	// TurnErrorStageStderrOutput — subprocess wrote non-empty stderr
	// on a clean exit; surfaced as a warning, not a failure.
	TurnErrorStageStderrOutput TurnErrorStage = "stderr_output"
	// TurnErrorStageStreamTruncated — the provider's event stream
	// ended without a terminal result event.
	TurnErrorStageStreamTruncated TurnErrorStage = "stream_truncated"
)

type TurnRequest

type TurnRequest struct {
	// Identity & framing
	AspectID           string         // who's running (cost/triage/identity attribution)
	AppendSystemPrompt string         // composed by funnel: NEXUS.md + SOUL.md + PRIMER + harness rules
	Session            SessionHandle  // opaque handle for provider-side state (subprocess-stream: resume key)
	SessionTail        []SessionEvent // recent events for direct-api providers to lower into the request

	// This turn
	UserMessage string      // the prompt that opens this turn (may be empty for autonomous)
	Inbox       []InboxItem // mid-turn comms accumulated since last turn

	// Tool surface
	Tools []ToolDef        // explicit in-process tool defs
	MCP   *MCPClientConfig // MCP-loaded tools; nil = no MCP tools this turn

	// Provider
	Provider ProviderID // claude-api | ollama-local | openai-api | claude-code
	Model    string     // REQUIRED — provider-specific model id; RunTurn returns ErrModelRequired if empty
	MaxSteps int        // hard cap on tool-call rounds; 0 = unlimited

	// ToolChoice optionally constrains how the model picks tools.
	// Empty string → provider default (typically "auto").
	// "auto" → model decides whether to call a tool.
	// "any" → model must call exactly one tool, free choice of which.
	// "none" → no tools may be called this turn (text only).
	// Any other value → name of a specific tool that must be called.
	// Not all providers honour all values; unsupported values fall back to "auto".
	ToolChoice string

	// Cwd is the working directory for subprocess-style providers
	// (currently claude-code). Empty falls through to the bridle host
	// process's cwd. Per-request rather than per-Harness because
	// different aspects sharing one Harness need distinct cwds —
	// claude-code derives its session jsonl path AND its .mcp.json
	// discovery from cwd, so two aspects with the same Harness but
	// overlapping cwds collide sessions and leak MCP identity from one
	// into the other. Direct-API providers (claude-api, ollama, openai)
	// ignore this field — they have no subprocess to anchor.
	Cwd string

	// ProviderEnv is per-call environment for the provider. Direct-API
	// providers read it as their auth/routing config (commonly
	// ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL, OPENAI_API_KEY,
	// OPENAI_BASE_URL); subprocess providers (claude-code) propagate it
	// into the spawned process's env so the same per-turn override
	// pattern applies. nil/empty = use whatever the provider already
	// has on its own (process env, --bare-style flags, etc).
	//
	// Per-call rather than per-process so a single funnel can mix
	// credentials across turns — e.g. main turn against the operator's
	// Anthropic credit pool, judge turn against a DeepSeek-via-
	// Anthropic-shape credential, eval turn against OpenAI. The
	// credential store wires this from aspects.default_*_credential
	// per task #218.
	ProviderEnv map[string]string
}

TurnRequest is the complete input for one deliberation turn.

type TurnResult

type TurnResult struct {
	FinalText     string           // model's last assistant text (may be empty for tool-only turns)
	ToolCalls     []ToolInvocation // ordered list of what the model actually did
	StepCount     int
	Usage         Usage
	StopReason    StopReason
	ResolvedModel string         // model id the upstream API reported; empty when unknown
	SessionDelta  []SessionEvent // events to propose to the funnel-owned JSONL
}

TurnResult is the structured outcome of a completed turn.

ResolvedModel is the model identifier the upstream API actually returned (Anthropic Messages.Model, OpenAI ChatCompletion.Model, claudecode result-event model, etc.). It can differ from TurnRequest.Model when per-turn ProviderEnv routes the call to a different backend — operator pool's Claude credit vs. DeepSeek-via- Anthropic-shape credential vs. OpenAI. Empty when the provider doesn't surface a model id. Callers attributing usage/cost/identity should prefer ResolvedModel when non-empty, fallback to TurnRequest.Model.

type Usage

type Usage struct {
	InputTokens              int
	OutputTokens             int
	CacheReadInputTokens     int     // Anthropic prompt-cache hit count
	CacheCreationInputTokens int     // tokens written into the prompt cache this turn
	CostUSD                  float64 // provider-reported or estimated; 0 if unknown
}

Usage holds token and cost data for a turn.

InputTokens is the count of UNCACHED prompt tokens billed at full rate. CacheReadInputTokens and CacheCreationInputTokens surface claude-api's prompt-caching behavior — the former is read at a discount, the latter is the new content being added to cache. Cache fields are zero for providers that don't expose caching (or don't run a cache-eligible request).

Sum (InputTokens + CacheReadInputTokens + CacheCreationInputTokens) approximates the total prompt size the model received. Use that for context-fullness reasoning; use InputTokens alone for billing estimates of fresh content.

Directories

Path Synopsis
Package fake provides scripted test doubles for the bridle harness.
Package fake provides scripted test doubles for the bridle harness.
internal
mcpclient
Package mcpclient wraps mark3labs/mcp-go to provide the bridle-internal MCP client used by direct-api providers.
Package mcpclient wraps mark3labs/mcp-go to provide the bridle-internal MCP client used by direct-api providers.
normalize
Package normalize provides helpers for mapping provider-specific wire values to bridle's canonical StopReason values.
Package normalize provides helpers for mapping provider-specific wire values to bridle's canonical StopReason values.
version
Package version holds the build-time version string for any binaries in this repo (today: just stubfunnel; future cmd helpers wire through here).
Package version holds the build-time version string for any binaries in this repo (today: just stubfunnel; future cmd helpers wire through here).
provider
bedrock
Package bedrock implements the bridle Provider interface for AWS Bedrock using the cross-model Converse API.
Package bedrock implements the bridle Provider interface for AWS Bedrock using the cross-model Converse API.
claude
Package claude implements the bridle Provider interface for the Anthropic Claude API.
Package claude implements the bridle Provider interface for the Anthropic Claude API.
claudecode
Package claudecode implements the bridle Provider interface using the claude-code CLI in headless mode (claude -p --output-format stream-json --verbose).
Package claudecode implements the bridle Provider interface using the claude-code CLI in headless mode (claude -p --output-format stream-json --verbose).
claudepty
Package claudepty implements the bridle Provider interface by spawning acp-claude-pty as a stdio subprocess and speaking ACP to it.
Package claudepty implements the bridle Provider interface by spawning acp-claude-pty as a stdio subprocess and speaking ACP to it.
gemini
Package gemini implements the bridle Provider interface for the Google Gemini API (Gemini Developer API or Vertex AI), via google.golang.org/genai.
Package gemini implements the bridle Provider interface for the Google Gemini API (Gemini Developer API or Vertex AI), via google.golang.org/genai.
geminicli
Package geminicli implements the bridle Provider interface using the gemini CLI in headless mode (gemini -p --output-format stream-json -y).
Package geminicli implements the bridle Provider interface using the gemini CLI in headless mode (gemini -p --output-format stream-json -y).
ollama
Package ollama implements the bridle Provider interface for a local Ollama server.
Package ollama implements the bridle Provider interface for a local Ollama server.
openai
Package openai implements the bridle Provider interface for the OpenAI API.
Package openai implements the bridle Provider interface for the OpenAI API.
Package stubfunnel is a temporary validation harness for bridle v0.1/patch1.
Package stubfunnel is a temporary validation harness for bridle v0.1/patch1.

Jump to

Keyboard shortcuts

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