Documentation
¶
Overview ¶
Package weft is a thin, opinionated core for building agents in Go.
Weft runs the agent loop — call a model, execute its tool calls in parallel with defined failure semantics, stream typed events while work is in flight — and nothing else. It is designed the way the standard library is: small interfaces, context everywhere, functional options, wrapped errors, and no required configuration.
A tool is a plain function; its JSON Schema is derived from the input struct. An agent is a value built once with options and run many times:
echo := weft.Tool("echo", "Echo a message",
func(ctx context.Context, in struct {
Msg string `json:"msg"`
}) (string, error) {
return "echo: " + in.Msg, nil
})
agt := weft.New(model, weft.Instructions("You are helpful."), echo)
res, err := agt.Generate(ctx, weft.Prompt("Say hi."))
A run ends when the model replies without tool calls or a StopWhen condition is met; MaxSteps is the safety budget behind both. Streaming is a range loop over typed events (Agent.Stream). Tool failures are data the model sees; only model failures, cancellation, and the step budget reach the caller, as *RunError.
The model seam (Model) is streaming-first; provider adapters translate vendor wire formats into weft's events. The wefttest package provides a scriptable Model for offline tests.
Index ¶
- Constants
- Variables
- func Manifest(agents ...*Agent) ([]byte, error)
- func ModelRequestsAllowed() bool
- type Agent
- type Call
- type Event
- type FilePart
- type Message
- type Model
- type ModelEvent
- type ModelFinish
- type ModelInfo
- type ModelReasoningDelta
- type ModelRequest
- type ModelTextDelta
- type ModelToolCall
- type Option
- func Instructions(text string) Option
- func MaxResultBytes(n int) Option
- func MaxSteps(n int) Option
- func Name(name string) Option
- func Parallelism(n int) Option
- func Sequential() Option
- func StopWhen(conds ...StopCondition) Option
- func Tap(fn func(ctx context.Context, ev Event)) Option
- func ToolSource(fn func() []*ToolDef) Option
- type Part
- type ReasoningDelta
- type ReasoningPart
- type Role
- type Run
- type RunError
- type RunFinish
- type RunOption
- type RunResult
- type RunStart
- type Schema
- type StepFinish
- type StepRecord
- type StepStart
- type StopCondition
- type StopFunc
- type StopReason
- type TextDelta
- type TextPart
- type ToolCallPart
- type ToolDef
- type ToolFinish
- type ToolResultPart
- type ToolStart
- type Usage
Examples ¶
Constants ¶
const SchemaVersion = 1
SchemaVersion is the version of the message wire format. The JSON encoding of Message and its parts is a compatibility contract: within a version, field names and shapes change only additively. Persistence and serving layers envelope messages with this number; the core itself never needs it.
Variables ¶
var ( // ErrMaxSteps is returned when the model still requests tools after the // last allowed step. The partial transcript rides along on RunError. ErrMaxSteps = errors.New("weft: run exceeded the maximum number of steps") // ErrNoSuchTool is returned by Agent.CallTool when the call names a // tool the agent does not have. Inside the loop the same condition is // folded into an error tool result — data for the model to // self-correct, not a run failure. ErrNoSuchTool = errors.New("weft: no tool with that name") // ErrInvalidToolInput is returned by ToolDef.Invoke and Agent.CallTool // when the arguments do not decode into the tool's input type. Like // ErrNoSuchTool, the loop turns it into model-visible data. ErrInvalidToolInput = errors.New("weft: tool input is not valid for its schema") // ErrRunConsumed is returned by Run.Events when the event stream has // already been consumed; each Run yields exactly one sequence. ErrRunConsumed = errors.New("weft: run events already consumed") // ErrModelContract is wrapped around failures of a Model // implementation to honor the stream contract documented on Model: // events after ModelFinish, a stream ending without one, a tool call // with an empty ID or name, or a panicking stream. It signals an // adapter bug, not a model outage — providers' own errors surface // unwrapped. ErrModelContract = errors.New("weft: model violated the stream contract") // ErrUnsupported is wrapped by a Model that cannot honour part of a // request — a FilePart whose media type the provider does not accept, // a feature the vendor lacks. It is a run error (the model call // fails), so callers can errors.Is on it and fall back to another // model. ErrUnsupported = errors.New("weft: request uses a feature the model does not support") // ErrStreamIdle is the stream error an adapter yields when the gap // between two chunks exceeds its IdleTimeout. The ctx deadline is the // hard limit on a whole call; the idle timeout only catches a stalled // stream, so a slow but actively streaming response is never killed. // It is a run error like any provider error; callers errors.Is on it // provider-agnostically, without knowing which adapter timed out. ErrStreamIdle = errors.New("weft: model stream idle timeout") // ErrModelRequestsDenied is the stream error every first-party // adapter yields from Stream when ModelRequestsAllowed is false — the // kill switch for test suites that must never reach the network. // wefttest models ignore the switch, so ordinary offline tests are // unaffected. ErrModelRequestsDenied = errors.New("weft: model requests denied by WEFT_MODEL_REQUESTS") )
Sentinel errors for named run failures. Branch on them with errors.Is; never match on error strings.
Functions ¶
func Manifest ¶
Manifest renders the agents as their `weft.json` document: one generated, committed, diffable description of every agent and tool. Studio, docs, review, and compatibility checks read a file instead of a live process; the code stays the only source of truth. The file is output, never input — nothing is configured from it. Generate it in a golden test (wefttest.Golden) so a stale file fails `go test`; run that test with `-update` to regenerate.
Tools are listed in registration order, agents in argument order, and encoding/json sorts map keys, so the bytes are deterministic for the same agents. Unnamed and duplicate agent names are errors: the file is a review artifact, and `agent_1` in a diff is noise.
Example ¶
The manifest is generated output: one committed, diffable description of every agent and tool. Gate it with a golden test so it cannot go stale.
package main
import (
"context"
"fmt"
"log"
"strings"
"github.com/weftgo/weft"
"github.com/weftgo/weft/wefttest"
)
func main() {
agt := weft.New(wefttest.Script(wefttest.Say("ok")),
weft.Name("support-bot"),
weft.Instructions("You are a support agent."),
weft.Tool("refund_order", "Refund a customer's order.",
func(_ context.Context, _ struct {
OrderID string `json:"order_id"`
}) (string, error) {
return "refunded", nil
}),
)
b, err := weft.Manifest(agt)
if err != nil {
log.Fatal(err)
}
lines := strings.Split(string(b), "\n")
fmt.Println(lines[0])
fmt.Println(lines[1])
}
Output: { "weft": 1,
func ModelRequestsAllowed ¶
func ModelRequestsAllowed() bool
ModelRequestsAllowed reports whether adapters may call a provider. It is false when WEFT_MODEL_REQUESTS=deny — the guard for test suites that must never reach the network (Pydantic AI's ALLOW_MODEL_REQUESTS). First-party adapters check it at the top of Stream and yield ErrModelRequestsDenied when it is false, before any network I/O; wefttest models ignore it, so ordinary offline tests are unaffected.
The environment is read on every call, not cached: a model call is network-bound so one getenv is noise, test suites can toggle the switch per test with t.Setenv, and the package keeps no state at all.
Types ¶
type Agent ¶
type Agent struct {
// contains filtered or unexported fields
}
Agent is an immutable, reusable value: a model, a system instruction, a tool set, and an execution policy. Build it once with New; run it many times, concurrently if you like — runs share no state.
Example (Generate) ¶
package main
import (
"context"
"fmt"
"log"
"github.com/weftgo/weft"
"github.com/weftgo/weft/wefttest"
)
func main() {
echo := weft.Tool("echo", "Echo a message.",
func(_ context.Context, in struct {
Msg string `json:"msg"`
}) (string, error) {
return "echo: " + in.Msg, nil
})
model := wefttest.Script(
wefttest.ToolCalls(wefttest.Call{Name: "echo", Args: `{"msg":"hello"}`}),
wefttest.Say("I echoed your message."),
)
agt := weft.New(model, weft.Instructions("You echo things."), echo)
res, err := agt.Generate(context.Background(), weft.Prompt("Echo hello."))
if err != nil {
log.Fatal(err)
}
fmt.Println(res.Text())
fmt.Println("steps:", res.NumSteps(), "tokens:", res.Usage.Total())
}
Output: I echoed your message. steps: 2 tokens: 30
Example (Stream) ¶
package main
import (
"context"
"fmt"
"log"
"github.com/weftgo/weft"
"github.com/weftgo/weft/wefttest"
)
func main() {
roll := weft.Tool("roll_dice", "Roll a six-sided die.",
func(_ context.Context, _ struct{}) (int, error) {
return 4, nil // deterministic for the example
})
model := wefttest.Script(
wefttest.ToolCalls(wefttest.Call{Name: "roll_dice"}),
wefttest.Say("You rolled a 4!"),
)
agt := weft.New(model, roll)
for ev, err := range agt.Stream(context.Background(), weft.Prompt("Roll a die.")).Events() {
if err != nil {
log.Fatal(err)
}
switch ev := ev.(type) {
case weft.ToolStart:
fmt.Println("tool:", ev.Name)
case weft.TextDelta:
fmt.Println("text:", ev.Text)
case weft.RunFinish:
fmt.Println("done in", ev.Steps, "steps")
}
}
}
Output: tool: roll_dice text: You rolled a 4! done in 2 steps
func New ¶
New builds an Agent. Nil models panic — including typed nils such as var m *someModel; New(m), which would otherwise crash much later inside a run goroutine. Everything else has a working default.
func (*Agent) CallTool ¶
CallTool dispatches one tool call by name, the way the loop does, and returns the result text the model would see. It is the seam for manual dispatchers and future tool middleware. Unlike the loop it does not contain failures: an unknown name returns an error wrapping ErrNoSuchTool, undecodable arguments one wrapping ErrInvalidToolInput, and handler errors and panics propagate.
func (*Agent) Generate ¶
Generate runs the agent to completion and returns the final result. Internally it is Stream with the events folded away; errors are returned as *RunError, with the partial transcript attached.
type Call ¶
type Call struct {
RunID string // the run this call belongs to
Step int // zero-based index of the step that requested it
CallID string // the provider's call identifier
Name string // the tool name
}
Call identifies the tool invocation a handler is serving. Retrieve it with CallFromContext — for audit logs, per-call idempotency keys, or progress reporting that must name its call.
type Event ¶
type Event interface {
// contains filtered or unexported methods
}
Event is the sealed set of run progress events, yielded by Run.Events in emission order. New event types may be added additively; external types cannot join, so switches over events stay exhaustively lintable.
On the wire every event carries a "type" discriminator (run_start, step_start, text_delta, reasoning_delta, tool_start, tool_finish, step_finish, run_finish) and UnmarshalEvent restores it — the same rule and the same compatibility contract as the message parts (ADR 0004).
func UnmarshalEvent ¶
UnmarshalEvent decodes one wire event, dispatching on its "type" discriminator. An unknown or missing type is an error, never a silent drop: a recorded stream must replay exactly what was emitted.
Example ¶
Recorded event streams decode back into typed events: store writes them, the Inspector replays them.
package main
import (
"fmt"
"log"
"github.com/weftgo/weft"
)
func main() {
ev, err := weft.UnmarshalEvent([]byte(`{"type":"tool_start","seq":5,"call_id":"c1","name":"echo","args":{"m":"x"}}`))
if err != nil {
log.Fatal(err)
}
start := ev.(weft.ToolStart)
fmt.Println(start.Name, start.CallID, start.Seq, string(start.Args))
}
Output: echo c1 5 {"m":"x"}
type FilePart ¶
type FilePart struct {
MediaType string `json:"media_type"`
Data []byte `json:"data,omitempty"`
URL string `json:"url,omitempty"`
}
FilePart is a file the user supplies to the model: an image, a PDF, audio. Exactly one of Data (inline; base64 on the wire via []byte's default encoding) or URL is set. Adapters map it to the vendor's image/document block; an adapter that cannot carry this MediaType fails the model call with an error wrapping ErrUnsupported. The core never reads the bytes. The exactly-one rule is documented, not enforced here — the adapter is the layer that knows what it can send, and it returns ErrUnsupported for a part with both or neither set.
func (FilePart) MarshalJSON ¶
MarshalJSON encodes the part with its "type" discriminator.
type Message ¶
Message is one turn in a conversation: a role plus an ordered list of content parts. A step's tool results are collected on a single RoleTool message; provider adapters fan out or merge as their wire format requires.
On the wire every part carries a "type" discriminator ("text", "tool_call", "tool_result", "reasoning"), so a Message round-trips through encoding/json losslessly.
func Repair ¶
Repair makes a transcript valid model input: every tool call has a result (missing ones become visible error results), results with no call are dropped, everything else is untouched. The loop applies it to the input of every run; store and runtime call it before persisting. It is pure (the input is never mutated) and idempotent: Repair(Repair(m)) equals Repair(m). nil input yields nil; an empty non-nil input yields an empty non-nil transcript.
Example ¶
A partial transcript (the run was interrupted mid-step) becomes valid model input: the missing result is synthesised, visibly.
package main
import (
"encoding/json"
"fmt"
"github.com/weftgo/weft"
)
func main() {
msgs := []weft.Message{
weft.User("Where is order 1234?"),
{Role: weft.RoleAssistant, Content: []weft.Part{
weft.ToolCallPart{ID: "c1", Name: "lookup_order", Args: json.RawMessage(`{"order_id":"1234"}`)},
}},
}
for _, m := range weft.Repair(msgs) {
fmt.Println(m.Role)
}
}
Output: user assistant tool
func UserParts ¶
UserParts returns a user message with the given parts, for prompts that mix text and files: UserParts(TextPart{"What is this?"}, FilePart{MediaType: "image/png", URL: u}). The parts are copied; the caller's slice is not retained.
Example ¶
Provider reasoning round-trips: it is preserved in the transcript, placed before the text of the same assistant turn. A prompt can mix text and files with UserParts; the core carries the bytes and the adapter maps them to the provider's image block.
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/weftgo/weft"
)
func main() {
msg := weft.UserParts(
weft.TextPart{Text: "What is this?"},
weft.FilePart{MediaType: "image/png", URL: "https://example.com/cat.png"},
)
b, err := json.Marshal(msg)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(b))
}
Output: {"role":"user","content":[{"type":"text","text":"What is this?"},{"type":"file","media_type":"image/png","url":"https://example.com/cat.png"}]}
func (*Message) UnmarshalJSON ¶
UnmarshalJSON decodes a message, dispatching each content part on its "type" discriminator. An unknown part type is an error: the wire format is versioned, and silently dropping content would corrupt transcripts.
type Model ¶
type Model interface {
Stream(ctx context.Context, req ModelRequest) iter.Seq2[ModelEvent, error]
}
Model is the provider seam. Implementations stream one step's output as events; first-party adapters wrap the vendors' official Go SDKs rather than re-implementing HTTP.
The stream contract:
- Events are yielded in order: any number of ModelTextDelta, ModelReasoningDelta, and ModelToolCall values, then exactly one ModelFinish.
- Failure is reported as a single terminal yield of (nil, err); no events follow it.
- The sequence honors ctx: when ctx is done, the model yields (nil, ctx.Err()) if it has not finished already.
The loop enforces this contract: a stream that ends without ModelFinish, continues after it, or panics fails the run with an error wrapping ErrModelContract. A contract-violating adapter cannot corrupt a transcript silently.
type ModelEvent ¶
type ModelEvent interface {
// contains filtered or unexported methods
}
ModelEvent is the sealed set of events a model yields during one step. Tool calls arrive whole — assembling providers' streamed argument fragments is the adapter's job, which is what makes the core's streaming uniform across providers.
type ModelFinish ¶
type ModelFinish struct {
Reason StopReason
Usage Usage
// Raw is the provider's own stop reason when Reason had to be
// approximated ("refusal", "pause_turn", "content_filter", ...).
// Empty when the mapping was exact. The core never interprets it; it
// is recorded on the step (StepRecord.RawStopReason) and the
// StepFinish event so callers can see a refusal without a tap.
Raw string
}
ModelFinish closes a step with its stop reason and token usage.
type ModelInfo ¶
type ModelInfo struct {
Provider string `json:"provider"` // "openai", "anthropic", "wefttest"
Name string `json:"name"` // the vendor's model id
}
ModelInfo identifies a model for telemetry (§8.1) and the manifest. A Model that can report it implements the optional
interface{ Info() ModelInfo }
which the loop detects and surfaces on RunStart.Model; Model itself stays one method. Middleware that wraps a Model should forward Info (TODO §4.1).
type ModelReasoningDelta ¶
ModelReasoningDelta is an increment of provider reasoning (Anthropic thinking, Gemini thought summaries). Signature is the provider's opaque token for the block, if any; adapters set it on the delta that completes a block. The core stores and forwards reasoning and never reads it.
Block boundaries: a delta carrying a non-empty Signature closes the current reasoning block; the next reasoning delta opens a new one. Providers send a block's signature last (Anthropic's signature_delta ends a thinking block; Gemini's per-part signature is emitted after the part's text), so one ReasoningPart per provider block survives the round trip. Reasoning without any signature accumulates into a single block — nothing downstream can send unsigned blocks back anyway, so their boundaries are not load-bearing.
type ModelRequest ¶
type ModelRequest struct {
System string
Messages []Message
Tools []*ToolDef
// SequentialTools asks the provider not to emit parallel tool-call
// batches. The zero value keeps the provider default; the loop sets
// it to true exactly under Sequential() (and Parallelism(1)), so the
// model does not emit batches the execution policy would serialize
// anyway. Adapters mirror it in the provider's parallel-tool-calls
// setting.
SequentialTools bool
}
ModelRequest is everything a model needs for one step: the system instruction, the transcript so far, and the callable tools.
Read-only: implementations must not modify Messages or Tools, and must clone anything they retain beyond the call.
type ModelTextDelta ¶
type ModelTextDelta struct {
Text string
}
ModelTextDelta is an increment of assistant text.
type ModelToolCall ¶
type ModelToolCall struct {
ID string
Name string
Args json.RawMessage
Signature string
}
ModelToolCall is one complete tool invocation request. ID is the provider's call identifier, echoed back on the matching ToolResultPart. Signature is the provider's opaque token attached to the call itself (Gemini attaches thought signatures to functionCall parts and requires them returned on the same part); adapters that do not have one leave it empty.
type Option ¶
type Option interface {
// contains filtered or unexported methods
}
Option configures an Agent at construction. Options are small values returned by Instructions, MaxSteps, Parallelism, Sequential, StopWhen, and the Tool constructor.
func Instructions ¶
Instructions sets the agent's system prompt.
func MaxResultBytes ¶
MaxResultBytes sets the maximum size of one tool result's text, in bytes (default 64 KiB). The loop caps longer results — successes, failures, and panics alike — cutting on a rune boundary and appending a marker the model can see, so it knows the output is partial. MaxResultBytes(0) removes the cap; negative values are ignored. Agent.CallTool returns uncapped output: the cap is a run policy, applied by the loop.
func MaxSteps ¶
MaxSteps is the safety budget: the most model calls a run may make (default 10). Exceeding it fails the run with ErrMaxSteps — a runaway loop is a failure to surface, never a quiet success. Use StopWhen for the intended end of a run. Values below 1 are ignored.
func Name ¶
Name names the agent: it appears on RunStart.Agent and in the manifest, which requires it (weft.Manifest errors on unnamed agents). Empty values are ignored.
func Parallelism ¶
Parallelism sets the maximum number of a step's tool calls executing at once (default 4). Values below 1 are ignored.
func Sequential ¶
func Sequential() Option
Sequential restricts a step's tool calls to run one at a time, in call order: each tool finishes before the next starts — the safe setting for tools with shared state. Under any parallelism, tools *start* in call order; Sequential additionally serializes their execution. It also sets ModelRequest.SequentialTools, so adapters ask the provider not to emit parallel batches in the first place.
func StopWhen ¶
func StopWhen(conds ...StopCondition) Option
StopWhen adds stop conditions; the run ends when any one is met. Without StopWhen a run ends when the model replies without requesting tools. Compose the built-ins — HasToolCall, StepCountIs — or write your own:
weft.StopWhen(weft.HasToolCall("submit_answer"))
weft.StopWhen(weft.StopFunc(func(steps []weft.StepRecord) bool { ... }))
Stop conditions are the intended end of a run; MaxSteps is the safety budget behind them.
func Tap ¶
Tap registers an observer that sees every event of every run, including runs made with Generate, synchronously and in emission order on the emitting goroutine. It must be fast and must not block: it runs under the event-ordering lock, so a slow tap delays every tool event of its step and blocks the emitting tool goroutines. Taps run in registration order; a panic in one is recovered and dropped, so a broken observer cannot break a run. Taps observe and cannot change anything — behaviour attaches at the two middleware seams. ctx is the run's context.
Example ¶
Tap observes every event of every run — including Generate, which has no stream to range over. Taps see; the middleware seams change.
package main
import (
"context"
"fmt"
"log"
"github.com/weftgo/weft"
"github.com/weftgo/weft/wefttest"
)
func main() {
var calls int
agt := weft.New(
wefttest.Script(
wefttest.ToolCalls(wefttest.Call{Name: "roll_dice"}),
wefttest.Say("rolled"),
),
weft.Tap(func(_ context.Context, ev weft.Event) {
if _, ok := ev.(weft.ToolStart); ok {
calls++
}
}),
weft.Tool("roll_dice", "Roll a die.",
func(_ context.Context, _ struct{}) (int, error) { return 4, nil }),
)
if _, err := agt.Generate(context.Background(), weft.Prompt("Roll.")); err != nil {
log.Fatal(err)
}
fmt.Println("tool calls:", calls)
}
Output: tool calls: 1
func ToolSource ¶
ToolSource replaces the tool set the loop advertises and dispatches against with the given function's return value, fetched fresh at each step — the seam for registries that change while the agent runs (plugins installed mid-run, MCP servers polled per step). The Agent stays immutable: the source is a value; synchronization and uniqueness of names belong to the source's owner. The function runs on the loop goroutine once per step for advertising and once per dispatched call; on duplicate names in the returned list the first entry wins. A nil function (the default) keeps the static construction-time list — Tool/option registration is then the only source of tools, byte-identical to an agent without a source. Manifest and Agent.Tools still report the static construction-time set: a manifest describes the code, not the registry behind a source.
type Part ¶
type Part interface {
// contains filtered or unexported methods
}
Part is one content part of a Message. The set of part types is closed: text, tool calls, tool results, reasoning, and files today; approval parts are planned additions that will join this interface.
type ReasoningDelta ¶
type ReasoningDelta struct {
Text string `json:"text"`
}
ReasoningDelta is an increment of provider reasoning, in the order the model produced it relative to TextDelta. Signatures are not streamed; they are on the ReasoningPart of the transcript.
func (ReasoningDelta) MarshalJSON ¶
func (e ReasoningDelta) MarshalJSON() ([]byte, error)
MarshalJSON encodes the event with its "type" discriminator.
type ReasoningPart ¶
type ReasoningPart struct {
Text string `json:"text"`
Signature string `json:"signature,omitempty"`
}
ReasoningPart is one provider reasoning block surfaced by providers that expose it (Anthropic thinking blocks, Gemini thought parts). Weft preserves it in the transcript but does not act on it. Signature is the provider's opaque token for the block (Anthropic rejects thinking sent back without its signature); adapters echo it unchanged. A step yields one ReasoningPart per provider block — a delta carrying a signature closes the block (see ModelReasoningDelta) — placed before the TextPart of the same assistant message, in the order the model produced them.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/weftgo/weft"
"github.com/weftgo/weft/wefttest"
)
func main() {
model := wefttest.Script(
wefttest.Think("The user greets; reply in kind.", wefttest.Say("Hello!")),
)
res, err := weft.New(model).Generate(context.Background(), weft.Prompt("Hi."))
if err != nil {
log.Fatal(err)
}
for _, p := range res.Messages[1].Content {
fmt.Printf("%T\n", p)
}
}
Output: weft.ReasoningPart weft.TextPart
func (ReasoningPart) MarshalJSON ¶
func (p ReasoningPart) MarshalJSON() ([]byte, error)
MarshalJSON encodes the part with its "type" discriminator.
type Role ¶
type Role string
Role is the author of a Message.
There is no system role: the system instruction is agent-level (Instructions) and travels on ModelRequest.System, so a transcript never carries it and adapters never have to merge it.
type Run ¶
type Run struct {
// contains filtered or unexported fields
}
Run is a handle to one streaming execution. Create it with Agent.Stream, then either range over Events (exactly once) or call Wait, which runs the agent to completion and reports the final result.
func (*Run) Events ¶
Events returns the run's event stream. It is single-use; a second call yields only ErrRunConsumed.
Events arrive in emission order (see ToolStart for the concurrent-tool ordering rule). A failed run delivers its error exactly once as the final element; a successful run ends with RunFinish. Breaking out of the range cancels the run.
type RunError ¶
RunError reports a step-scoped failure: the model stream failed, the context was canceled, or the step budget ran out. Err is the cause — use errors.Is/As on it. Result carries the transcript up to the failure, so partial work is never lost.
type RunFinish ¶
RunFinish is always the final event of a successful run and carries the run's total usage and step count.
func (RunFinish) MarshalJSON ¶
MarshalJSON encodes the event with its "type" discriminator.
type RunOption ¶
type RunOption interface {
// contains filtered or unexported methods
}
RunOption configures a single run.
type RunResult ¶
type RunResult struct {
ID string
// StopReason is the last step's finish reason. StopMaxTokens here
// means the final reply was cut off by the output-token limit — the
// run still succeeds, and the caller decides what truncated text
// means.
StopReason StopReason
Messages []Message
Steps []StepRecord
Usage Usage
}
RunResult is the outcome of a completed run: its id, the full transcript (including the input messages), one record per step, and summed usage.
type RunStart ¶
type RunStart struct {
ID string `json:"id"`
Model ModelInfo `json:"model"`
Agent string `json:"agent,omitempty"`
}
RunStart is always the first event of a run and carries its id and, when reported, the model's identity and the agent's name.
func (RunStart) MarshalJSON ¶
MarshalJSON encodes the event with its "type" discriminator.
type Schema ¶
type Schema struct {
Type string `json:"type,omitempty"`
Format string `json:"format,omitempty"`
Description string `json:"description,omitempty"`
Properties map[string]*Schema `json:"properties,omitempty"`
Required []string `json:"required,omitempty"`
Items *Schema `json:"items,omitempty"`
}
Schema is the subset of JSON Schema (draft 2020-12) that weft derives from tool input structs. Its shape tracks what the official Go MCP SDK derives via google/jsonschema-go, so tools cross over to MCP without conversion.
type StepFinish ¶
type StepFinish struct {
Index int `json:"index"`
Reason StopReason `json:"reason"`
Usage Usage `json:"usage"`
Raw string `json:"raw,omitempty"`
}
StepFinish reports that step Index's model call completed. Raw is the provider's own stop reason when Reason was approximated (see ModelFinish.Raw); empty when the mapping was exact.
func (StepFinish) MarshalJSON ¶
func (e StepFinish) MarshalJSON() ([]byte, error)
MarshalJSON encodes the event with its "type" discriminator.
type StepRecord ¶
type StepRecord struct {
Index int
// StopReason is the mapped reason (stop, tool_calls, max_tokens);
// RawStopReason is the provider's own value when the mapping was
// approximated ("refusal", "content_filter", ...) — see
// ModelFinish.Raw.
StopReason StopReason
RawStopReason string
Usage Usage
Text string
ToolCalls []ToolCallPart
Results []ToolResultPart
}
StepRecord captures everything one model step produced: its text, the tool calls it requested, and the results of executing them in call order.
type StepStart ¶
type StepStart struct {
Index int `json:"index"`
}
StepStart reports that the model is being called for step Index.
func (StepStart) MarshalJSON ¶
MarshalJSON encodes the event with its "type" discriminator.
type StopCondition ¶
type StopCondition interface {
Stop(steps []StepRecord) bool
}
StopCondition decides, after a step's tool calls have run, whether the run is complete. It sees every step so far; the last element is the step just finished. Returning true ends the run successfully without another model call. The built-ins — HasToolCall, StepCountIs — also implement fmt.Stringer so the manifest (TODO §2.9) can name them; adapt an ordinary function with StopFunc.
func HasToolCall ¶
func HasToolCall(names ...string) StopCondition
HasToolCall stops the run once the step just finished called any of the named tools — the "final answer tool" pattern.
func StepCountIs ¶
func StepCountIs(n int) StopCondition
StepCountIs stops the run after exactly n steps, successfully — unlike MaxSteps, which treats reaching the budget as a failure.
type StopFunc ¶
type StopFunc func(steps []StepRecord) bool
StopFunc adapts an ordinary function to a StopCondition, the http.HandlerFunc shape.
func (StopFunc) Stop ¶
func (f StopFunc) Stop(steps []StepRecord) bool
Stop ends the run when f says so.
type StopReason ¶
type StopReason string
StopReason is why a model step ended.
const ( StopEndTurn StopReason = "stop" StopToolCalls StopReason = "tool_calls" StopMaxTokens StopReason = "max_tokens" )
type TextDelta ¶
type TextDelta struct {
Text string `json:"text"`
}
TextDelta is an increment of assistant text.
func (TextDelta) MarshalJSON ¶
MarshalJSON encodes the event with its "type" discriminator.
type TextPart ¶
type TextPart struct {
Text string `json:"text"`
}
TextPart is a span of user or assistant text.
func (TextPart) MarshalJSON ¶
MarshalJSON encodes the part with its "type" discriminator.
type ToolCallPart ¶
type ToolCallPart struct {
ID string `json:"id"`
Name string `json:"name"`
Args json.RawMessage `json:"args"`
Signature string `json:"signature,omitempty"`
}
ToolCallPart is a tool invocation requested by the model. Args is the raw JSON the model produced; the loop unmarshals it into the tool's input type before invoking the handler. Signature is the provider's opaque token attached to the call itself (Gemini's thought signatures ride functionCall parts and must return on the same part); empty for providers without one.
func (ToolCallPart) MarshalJSON ¶
func (p ToolCallPart) MarshalJSON() ([]byte, error)
MarshalJSON encodes the part with its "type" discriminator.
type ToolDef ¶
type ToolDef struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
InputSchema *Schema `json:"input_schema,omitempty"`
OutputSchema *Schema `json:"output_schema,omitempty"`
// contains filtered or unexported fields
}
ToolDef is a named, schema-described tool the model can call. Build one with the generic Tool constructor; the agent loop (and any manual dispatcher) executes it through Invoke.
func RawTool ¶
func RawTool(name, description string, schema *Schema, fn func(ctx context.Context, args json.RawMessage) (string, error)) *ToolDef
RawTool defines a tool from an explicit schema instead of reflection — for tools defined outside Go source: plugin manifests, MCP remotes, gateways. fn receives the model's raw JSON arguments verbatim: no unmarshalling, no ErrInvalidToolInput — validation belongs to fn. A nil schema becomes the empty object schema, so the tool accepts any object input. Panics match Tool: empty name, nil fn. The manifest records no source line (the definition is not in Go source).
func Tool ¶
func Tool[In, Out any](name, description string, fn func(ctx context.Context, in In) (Out, error)) *ToolDef
Tool defines a tool from a plain function. In and Out are inferred from the handler and the input schema is reflected from In's struct tags, so the compiler checks the handler's shape and nothing is written twice:
type RefundInput struct {
OrderID string `json:"order_id" jsonschema:"the order to refund"`
Reason string `json:"reason,omitempty"`
}
weft.Tool("refund_order", "Refund a customer's order",
func(ctx context.Context, in RefundInput) (Receipt, error) {
return billing.Refund(ctx, in.OrderID, in.Reason)
})
What the model sees as the result is the text of a string Out, and the JSON encoding of any other Out. Inside the handler, CallFromContext reports which call is running.
The handler shape mirrors the official Go MCP SDK's AddTool[In, Out], so a weft tool can be exposed over MCP without an adapter layer.
Tool panics if name is empty, fn is nil, or In is not a struct (or a pointer to one): providers and MCP require an object at the top level of a tool schema, and a scalar there would fail every real call.
Example ¶
A tool is a plain function; the input schema is derived from the struct.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"github.com/weftgo/weft"
)
func main() {
type WeatherInput struct {
City string `json:"city" jsonschema:"the city to look up"`
Days *int `json:"days,omitempty"`
}
getWeather := weft.Tool("get_weather", "Get a forecast.",
func(_ context.Context, in WeatherInput) (string, error) {
return "sunny in " + in.City, nil
})
b, err := json.MarshalIndent(getWeather.InputSchema, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(getWeather.Name)
fmt.Println(string(b))
}
Output: get_weather { "type": "object", "properties": { "city": { "type": "string", "description": "the city to look up" }, "days": { "type": "integer" } }, "required": [ "city" ] }
func (*ToolDef) Invoke ¶
Invoke runs the tool with raw JSON arguments: it unmarshals into the handler's input type, calls the handler, and returns the result text the model will see (a string output verbatim, anything else JSON-encoded). A decode failure returns an error wrapping ErrInvalidToolInput.
type ToolFinish ¶
type ToolFinish struct {
Seq int64 `json:"seq"`
CallID string `json:"call_id"`
Name string `json:"name"`
Content string `json:"content"`
IsError bool `json:"is_error"`
}
ToolFinish reports that a tool invocation completed, successfully or not. Content is the tool's JSON output, or the failure text when IsError is set — the same value the model sees on the matching ToolResultPart, so a UI can render results as they land.
func (ToolFinish) MarshalJSON ¶
func (e ToolFinish) MarshalJSON() ([]byte, error)
MarshalJSON encodes the event with its "type" discriminator.
type ToolResultPart ¶
type ToolResultPart struct {
CallID string `json:"call_id"`
Name string `json:"name"`
Content string `json:"content"`
IsError bool `json:"is_error"`
}
ToolResultPart is the outcome of one tool call, returned to the model as data. Content is the JSON encoding of the tool's output, or the failure message when IsError is set. Tool failures never abort a run; the model sees them and can recover.
func (ToolResultPart) MarshalJSON ¶
func (p ToolResultPart) MarshalJSON() ([]byte, error)
MarshalJSON encodes the part with its "type" discriminator.
type ToolStart ¶
type ToolStart struct {
Seq int64 `json:"seq"`
CallID string `json:"call_id"`
Name string `json:"name"`
Args json.RawMessage `json:"args"`
}
ToolStart reports that a tool invocation began. Events from tools running in parallel interleave: pair them by CallID and order by Seq, a per-run counter assigned at emission that totally orders the stream.
func (ToolStart) MarshalJSON ¶
MarshalJSON encodes the event with its "type" discriminator.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
anthropic
module
|
|
|
core
module
|
|
|
examples
|
|
|
getting-started
command
Command getting-started runs a complete weft agent offline: a scripted model (from wefttest), one tool, and the full event stream.
|
Command getting-started runs a complete weft agent offline: a scripted model (from wefttest), one tool, and the full event stream. |
|
google
module
|
|
|
mcp
module
|
|
|
obsdb
module
|
|
|
clickhouse
module
|
|
|
openai
module
|
|
|
otel
module
|
|
|
runtime
module
|
|
|
store
module
|
|
|
studio
module
|
|
|
cmd
module
|
|
|
thread
module
|
|
|
sqlite
module
|
|
|
Package wefttest provides a scriptable weft.Model, in the spirit of httptest: write the agent's dialogue as a sequence of scripted model steps and test agents offline, deterministically, with no network.
|
Package wefttest provides a scriptable weft.Model, in the spirit of httptest: write the agent's dialogue as a sequence of scripted model steps and test agents offline, deterministically, with no network. |
|
conformance
Package conformance is the provider adapter contract as an executable table.
|
Package conformance is the provider adapter contract as an executable table. |