Documentation
¶
Overview ¶
Package core is the native Go smooth-operator engine — an in-process agentic tool-calling loop over an OpenAI-compatible chat client, with in-memory knowledge grounding. The Go sibling of the Rust reference engine, the C# core, the Python core, and the TypeScript core. See docs/Architecture/Go Core.md.
Index ¶
- Constants
- Variables
- type AgentOptions
- type AgentRunResponse
- type Cast
- type ChatChunk
- type ChatClient
- type ChatMessage
- type ChatRequest
- type ChatResponse
- type Checkpoint
- type CheckpointStore
- type Clearance
- type CostBudget
- type CostTracker
- type Embedder
- type FuncTool
- type GatewayClient
- type HashEmbedder
- type HumanApprovalRequest
- type HumanApprovalResponse
- type HumanDecision
- type HumanGate
- type InMemoryCheckpointStore
- type InMemoryKnowledge
- type InMemoryMemory
- type Knowledge
- type KnowledgeHit
- type LexicalReranker
- type LlmProvider
- type Memory
- type MemoryEntry
- type MockLlmProvider
- func (m *MockLlmProvider) CallCount() int
- func (m *MockLlmProvider) Calls() []ChatRequest
- func (m *MockLlmProvider) Chat(_ context.Context, req ChatRequest) (ChatResponse, error)
- func (m *MockLlmProvider) ChatStream(_ context.Context, req ChatRequest) (<-chan ChatChunk, error)
- func (m *MockLlmProvider) LastCall() (ChatRequest, bool)
- func (m *MockLlmProvider) PushError(message string) *MockLlmProvider
- func (m *MockLlmProvider) PushResponse(resp ChatResponse) *MockLlmProvider
- func (m *MockLlmProvider) PushText(content string) *MockLlmProvider
- func (m *MockLlmProvider) PushToolCall(id, name, arguments string) *MockLlmProvider
- type ModelPricing
- type NodeFn
- type NoopReranker
- type OperatorRole
- type Reranker
- type RoleKind
- type Router
- type SmoothAgent
- func (a *SmoothAgent) Run(ctx context.Context, message string, history []ChatMessage) (AgentRunResponse, error)
- func (a *SmoothAgent) RunStream(ctx context.Context, message string, thread *SmoothAgentThread) (*Stream, error)
- func (a *SmoothAgent) RunThread(ctx context.Context, message string, thread *SmoothAgentThread) (AgentRunResponse, error)
- type SmoothAgentThread
- type Stream
- type StreamEvent
- type StreamEventKind
- type StreamingChatClient
- type Tool
- type ToolCall
- type ToolCallDelta
- type ToolSearch
- func (s *ToolSearch) Description() string
- func (s *ToolSearch) Execute(_ context.Context, args map[string]any) (string, error)
- func (s *ToolSearch) HasDeferred() bool
- func (s *ToolSearch) IsPromoted(name string) bool
- func (s *ToolSearch) Name() string
- func (s *ToolSearch) Parameters() map[string]any
- func (s *ToolSearch) Promote(name string) bool
- func (s *ToolSearch) PromotedTools() []Tool
- func (s *ToolSearch) ToolByName(name string) (Tool, bool)
- type ToolSpec
- type Usage
- type VectorKnowledge
- type Workflow
- func (w *Workflow[S]) AddConditionalEdge(from string, router Router[S]) *Workflow[S]
- func (w *Workflow[S]) AddEdge(from, to string) *Workflow[S]
- func (w *Workflow[S]) AddNode(name string, fn NodeFn[S]) *Workflow[S]
- func (w *Workflow[S]) Run(ctx context.Context, initialState S) (S, error)
- func (w *Workflow[S]) SetEnd(from string) *Workflow[S]
- func (w *Workflow[S]) SetEntry(name string) *Workflow[S]
Constants ¶
const END = "__end__"
END is the sentinel a conditional router returns to signal termination.
Variables ¶
var DefaultPricing = map[string]ModelPricing{
"claude-haiku-4-5": {InputPerMTok: 1.0, OutputPerMTok: 5.0},
"claude-sonnet-4-5": {InputPerMTok: 3.0, OutputPerMTok: 15.0},
}
DefaultPricing is approximate (USD / 1M tokens). Override via AgentOptions.Pricing.
Functions ¶
This section is empty.
Types ¶
type AgentOptions ¶
type AgentOptions struct {
Instructions string
Model string
MaxIterations int
MaxTokens int
Temperature float64
Knowledge Knowledge
KnowledgeTopK int
// Reranker reorders retrieved hits before injection (nil = passthrough).
Reranker Reranker
// KnowledgeCandidateK is the pool size retrieved before reranking; when greater
// than KnowledgeTopK, more docs are fetched, reranked, then trimmed to TopK.
KnowledgeCandidateK int
// Memory, if set, recalls relevant facts into context each turn.
Memory Memory
// MemoryTopK is how many memory entries to recall per turn (0 = default 4).
MemoryTopK int
Tools []Tool
// ParallelToolCalls, when true and an assistant turn returns >=2 tool calls,
// dispatches them concurrently (goroutines + sync.WaitGroup) instead of
// sequentially. Tool-result messages are still appended in the original
// ToolCalls order, so the transcript stays deterministic regardless of
// completion order. Default false preserves the sequential behaviour. Per-tool
// semantics (clearance, human-gate approval, tool_search promotion, JSON
// parsing, error handling) are unchanged — only the dispatch loop runs in parallel.
ParallelToolCalls bool
// DeferredTools are registered but with their schemas HIDDEN from the model.
// When any are present, a built-in tool_search meta-tool is advertised in their
// place; the model calls it to fuzzy-match and promote the ones it needs, which
// then become visible + dispatchable on subsequent turns. Keeps the tool schema
// payload small when there are many rarely-used tools. An unpromoted deferred
// tool is NOT dispatchable.
DeferredTools []Tool
// MaxContextTokens is the approximate token budget for the context window.
// Before each model call, older non-system messages are dropped (sliding
// window) to stay under it. 0 uses the default (8000); negative disables.
MaxContextTokens int
// Budget, if set, stops the turn early once accumulated usage/cost hits it.
Budget *CostBudget
// Pricing overrides the per-model cost table (defaults to DefaultPricing).
Pricing map[string]ModelPricing
// CheckpointStore, with ConversationID, persists/resumes the conversation.
CheckpointStore CheckpointStore
// ConversationID keys the checkpoint store (required to use checkpointing).
ConversationID string
// Clearance, if set, gates which tools may be dispatched. A tool the clearance
// forbids is not executed — a "tool not permitted" result is returned to the
// model instead. Nil allows every tool (the prior behaviour).
Clearance *Clearance
// HumanGate, when set, is asked for approval before running any tool call for
// which RequiresApproval returns true. A denied call is not executed; the model
// is told it was denied and can adapt.
HumanGate HumanGate
// RequiresApproval reports which tool calls need human approval (e.g. writes /
// destructive actions), given the tool name and parsed arguments. nil = none.
// Only consulted when HumanGate is set. Example:
//
// func(name string, _ map[string]any) bool { return name == "delete_record" }
RequiresApproval func(name string, args map[string]any) bool
// MaxRetries is the number of ADDITIONAL attempts after the first if the model
// call returns a transient error (rate-limit, 5xx, dropped connection). 0 (the
// default) preserves today's behaviour: a single attempt, error returned
// immediately. Only the model call is retried — never tool execution.
MaxRetries int
// RetryBackoff is the base delay for exponential backoff between retries. The
// wait before retry attempt n (1-indexed) is RetryBackoff * 2^(n-1). The zero
// value means no real delay (retries fire immediately) — which is what tests use
// so they don't sleep; production should set a small base such as 200ms.
RetryBackoff time.Duration
}
AgentOptions configures a SmoothAgent turn. Mirrors the sibling cores' options.
type AgentRunResponse ¶
type AgentRunResponse struct {
Text string
Iterations int
ToolCalls int
Usage Usage
CostUSD float64
// BudgetExceeded is true if the turn stopped because the cost/token budget was hit.
BudgetExceeded bool
}
AgentRunResponse is the result of a turn.
type Cast ¶
type Cast struct {
// contains filtered or unexported fields
}
Cast is the registered set of roles a lead can dispatch to. Mirrors the reference engines' Cast.
func (*Cast) Get ¶
func (c *Cast) Get(name string) (OperatorRole, bool)
Get returns the role by name and whether it was found.
func (*Cast) List ¶
func (c *Cast) List() []OperatorRole
List returns all roles in registration order.
func (*Cast) ListVisible ¶
func (c *Cast) ListVisible() []OperatorRole
ListVisible returns the non-hidden roles in registration order.
func (*Cast) Register ¶
func (c *Cast) Register(role OperatorRole) *Cast
Register adds (or replaces) a role and returns the cast for chaining.
func (*Cast) Sidekicks ¶
func (c *Cast) Sidekicks() []OperatorRole
Sidekicks returns the sidekick roles in registration order.
type ChatChunk ¶
type ChatChunk struct {
// ContentDelta is an incremental piece of assistant text ("" when this chunk
// carries no text).
ContentDelta string
// ToolCallDeltas are incremental tool-call fragments in this chunk.
ToolCallDeltas []ToolCallDelta
// Usage, when non-nil, reports cumulative token usage (gateways send it last).
Usage *Usage
}
ChatChunk is one streamed chunk from a streaming chat completion — the standard OpenAI streaming chunk shape (the slice the agent reads). Content deltas concatenate into the assistant text; tool-call fragments are assembled by their Index (ID + Function.Name appear when a call first opens, Function.Arguments arrives in fragments). Usage is non-nil on (typically) the final chunk.
type ChatClient ¶
type ChatClient interface {
Chat(ctx context.Context, req ChatRequest) (ChatResponse, error)
}
ChatClient is the minimal OpenAI-compatible surface the agent needs. The GatewayClient implements it against a live endpoint; tests inject a fake.
type ChatMessage ¶
type ChatMessage struct {
Role string
Content string
ToolCalls []ToolCall
ToolCallID string // set on role=="tool" messages
}
ChatMessage is one message in the OpenAI-shaped conversation.
type ChatRequest ¶
type ChatRequest struct {
Model string
Messages []ChatMessage
Tools []ToolSpec
Temperature float64
MaxTokens int
}
ChatRequest is a single model call.
type ChatResponse ¶
ChatResponse is the assistant's reply (content and/or tool calls).
func TextResponse ¶
func TextResponse(content string) ChatResponse
TextResponse builds a plain-text ChatResponse (no tool calls). Handy for scripting the mock and for assertions.
func ToolCallResponse ¶
func ToolCallResponse(id, name, arguments string) ChatResponse
ToolCallResponse builds a ChatResponse that requests a single tool call. arguments is the raw JSON-string the model emits for the call's arguments.
func WithUsage ¶
func WithUsage(resp ChatResponse, promptTokens, completionTokens int) ChatResponse
WithUsage returns a copy of resp with the given token usage attached. Handy when scripting the mock so the streaming final chunk / non-streaming Usage is non-zero.
type Checkpoint ¶
type Checkpoint struct {
ConversationID string
Messages []ChatMessage
}
Checkpoint is a saved conversation: its id and the non-system messages so far.
type CheckpointStore ¶
type CheckpointStore interface {
Save(cp Checkpoint)
Load(conversationID string) (Checkpoint, bool)
}
CheckpointStore persists and restores conversations by id.
type Clearance ¶
type Clearance struct {
// contains filtered or unexported fields
}
Clearance is a tool-access policy for a role. A deny always wins; a non-empty AllowTools is a whitelist; empty allow + empty deny means "all tools". DenyEverything blocks every tool regardless of the lists.
func AllowAllClearance ¶
func AllowAllClearance() Clearance
AllowAllClearance permits every tool (the zero-value default).
func AllowClearance ¶
AllowClearance whitelists exactly the named tools.
func DenyClearance ¶
DenyClearance blocks the named tools (everything else allowed).
func NewClearance ¶
NewClearance builds a clearance from explicit allow/deny lists and the deny-everything flag.
type CostBudget ¶
CostBudget is a ceiling for a turn. A zero field means "unset"; the first non-zero limit that is hit stops the turn.
type CostTracker ¶
CostTracker accumulates usage + cost across a turn's model calls.
func (*CostTracker) Exceeds ¶
func (t *CostTracker) Exceeds(b *CostBudget) bool
Exceeds reports whether the accumulated usage/cost has hit the budget.
func (*CostTracker) Record ¶
func (t *CostTracker) Record(model string, u Usage, pricing map[string]ModelPricing)
Record adds a model call's usage (and its cost, if the model is priced).
type FuncTool ¶
type FuncTool struct {
ToolName string
Desc string
Params map[string]any
Fn func(ctx context.Context, args map[string]any) (string, error)
}
FuncTool wraps a function as a Tool (the AIFunctionFactory analogue).
func (FuncTool) Description ¶
func (FuncTool) Parameters ¶
type GatewayClient ¶
type GatewayClient struct {
BaseURL string // e.g. https://llm.smoo.ai/v1
APIKey string
HTTPClient *http.Client
}
GatewayClient is an OpenAI-compatible ChatClient over HTTP — the Go analogue of the other cores' OpenAI SDK client. Points at any /chat/completions endpoint (e.g. the SmooAI gateway) with a Bearer API key.
(Phase 0 uses net/http directly, mirroring how the sibling cores' OpenAI SDKs do their own HTTP. Adopting @smooai/fetch's Go client is a tracked follow-up.)
func NewGatewayClient ¶
func NewGatewayClient(baseURL, apiKey string) *GatewayClient
NewGatewayClient builds a client for the given base URL + key.
func (*GatewayClient) Chat ¶
func (g *GatewayClient) Chat(ctx context.Context, req ChatRequest) (ChatResponse, error)
Chat implements ChatClient.
func (*GatewayClient) ChatStream ¶
func (g *GatewayClient) ChatStream(ctx context.Context, req ChatRequest) (<-chan ChatChunk, error)
ChatStream implements StreamingChatClient: it opens an SSE streaming completion and translates each `data: {...}` line into a ChatChunk on the returned channel. Connect-time failures (request build / HTTP / non-2xx) come back as the error; the channel is closed when the SSE stream ends (`data: [DONE]` or EOF).
type HashEmbedder ¶
type HashEmbedder struct {
Dim int
}
HashEmbedder is a deterministic, offline feature-hashing embedder. It hashes each token into one of dim buckets (signed) and L2-normalizes. No learned semantics, but a real vector with cosine geometry — docs sharing tokens land near each other.
func NewHashEmbedder ¶
func NewHashEmbedder(dim int) HashEmbedder
NewHashEmbedder returns a HashEmbedder with the given dimension (default 256).
func (HashEmbedder) Embed ¶
func (e HashEmbedder) Embed(text string) []float64
Embed hashes tokens into a normalized vector.
type HumanApprovalRequest ¶
HumanApprovalRequest is sent before the agent executes a sensitive/write tool. Mirrors the C# HumanApprovalRequest / the Rust engine's HumanRequest::Confirm.
type HumanApprovalResponse ¶
type HumanApprovalResponse struct {
Decision HumanDecision
Reason string
}
HumanApprovalResponse is the answer to a HumanApprovalRequest. Mirrors the C# HumanApprovalResponse.
func Deny ¶
func Deny(reason string) HumanApprovalResponse
Deny builds a denial carrying a reason the model will see.
func (HumanApprovalResponse) IsApproved ¶
func (r HumanApprovalResponse) IsApproved() bool
IsApproved reports whether the decision was HumanApproved.
type HumanDecision ¶
type HumanDecision int
HumanDecision is the human's verdict on a tool call that required approval.
const ( // HumanApproved lets the tool run. HumanApproved HumanDecision = iota // HumanDenied blocks the tool; the reason is fed back to the model. HumanDenied )
type HumanGate ¶
type HumanGate func(ctx context.Context, request HumanApprovalRequest) (HumanApprovalResponse, error)
HumanGate is the human-in-the-loop seam: the agent consults it before running any tool that AgentOptions.RequiresApproval flags. The implementation IS the pause point — wire it to a UI awaiting a real person, or decide programmatically. Mirrors the C# IHumanGate (here a func, the idiomatic Go seam).
type InMemoryCheckpointStore ¶
type InMemoryCheckpointStore struct {
// contains filtered or unexported fields
}
InMemoryCheckpointStore is a process-local store backed by a map.
func NewInMemoryCheckpointStore ¶
func NewInMemoryCheckpointStore() *InMemoryCheckpointStore
NewInMemoryCheckpointStore creates an empty in-memory store.
func (*InMemoryCheckpointStore) Load ¶
func (s *InMemoryCheckpointStore) Load(conversationID string) (Checkpoint, bool)
Load returns the checkpoint for an id, and whether one was found.
func (*InMemoryCheckpointStore) Save ¶
func (s *InMemoryCheckpointStore) Save(cp Checkpoint)
Save persists a copy of the checkpoint's messages.
type InMemoryKnowledge ¶
type InMemoryKnowledge struct {
// contains filtered or unexported fields
}
InMemoryKnowledge is a tiny lexical-overlap knowledge base — Phase-0 parity with the reference engines' in-memory lexical store (not a vector store).
func (*InMemoryKnowledge) Ingest ¶
func (k *InMemoryKnowledge) Ingest(content, source string)
Ingest adds a document to the knowledge base.
func (*InMemoryKnowledge) Query ¶
func (k *InMemoryKnowledge) Query(query string, topK int) []KnowledgeHit
Query returns up to topK documents, ranked by token overlap with the query. When nothing overlaps, the first topK documents are returned anyway so the agent still has context to ground (or honestly decline) against.
type InMemoryMemory ¶
type InMemoryMemory struct {
// contains filtered or unexported fields
}
InMemoryMemory is a process-local memory pool with lexical-overlap recall.
func (*InMemoryMemory) Recall ¶
func (m *InMemoryMemory) Recall(query string, topK int) []MemoryEntry
Recall returns up to topK entries that share terms with the query, best first.
func (*InMemoryMemory) Remember ¶
func (m *InMemoryMemory) Remember(text string)
Remember adds a fact (blank entries are ignored).
type Knowledge ¶
type Knowledge interface {
Query(query string, topK int) []KnowledgeHit
}
Knowledge is a retriever: returns the most relevant documents for a query. Both the lexical InMemoryKnowledge and the embedding-backed VectorKnowledge satisfy this, so the agent accepts either.
type KnowledgeHit ¶
KnowledgeHit is one retrieved document with its lexical-overlap score.
type LexicalReranker ¶
type LexicalReranker struct{}
LexicalReranker reorders by query-term coverage normalized by document length: coverage / log2(2 + docTokenCount), so coverage is rewarded but long documents are penalized relative to concise ones with the same coverage. Stable for ties.
func (LexicalReranker) Rerank ¶
func (LexicalReranker) Rerank(query string, hits []KnowledgeHit) []KnowledgeHit
Rerank reorders hits by the normalized-coverage score.
type LlmProvider ¶
type LlmProvider = ChatClient
LlmProvider is the LLM-call seam the agent loop depends on. It is the existing ChatClient interface under a name that names the role — formalizing the seam so the agent loop is unit-testable deterministically, without a live model or network. The GatewayClient already implements it; tests inject a MockLlmProvider.
This keeps backward compatibility: NewSmoothAgent still takes a ChatClient, and any LlmProvider is a ChatClient (and vice versa) since they are the same shape.
type Memory ¶
type Memory interface {
Remember(text string)
Recall(query string, topK int) []MemoryEntry
}
Memory is a pool of remembered facts, recalled by relevance to a query.
type MockLlmProvider ¶
type MockLlmProvider struct {
// contains filtered or unexported fields
}
MockLlmProvider is a deterministic LlmProvider for tests. Script the responses it should return (FIFO), drive your code, then assert on Calls. Build it up fluently with PushText / PushToolCall / PushError.
It replaces the ad-hoc fakeClient the tests rolled by hand, and mirrors the BEHAVIOR of the Rust reference's MockLlmClient (record + replay). It is not safe for concurrent use — a turn drives it serially, which is the intended use.
func NewMockLlmProvider ¶
func NewMockLlmProvider() *MockLlmProvider
NewMockLlmProvider returns an empty mock. Script it with the Push* methods.
func (*MockLlmProvider) CallCount ¶
func (m *MockLlmProvider) CallCount() int
CallCount returns the number of requests received.
func (*MockLlmProvider) Calls ¶
func (m *MockLlmProvider) Calls() []ChatRequest
Calls returns every request the mock has received so far, in order.
func (*MockLlmProvider) Chat ¶
func (m *MockLlmProvider) Chat(_ context.Context, req ChatRequest) (ChatResponse, error)
Chat implements ChatClient / LlmProvider: it records the request, then replays the next scripted outcome. With an empty script it returns a benign empty text response so loops terminate cleanly.
func (*MockLlmProvider) ChatStream ¶
func (m *MockLlmProvider) ChatStream(_ context.Context, req ChatRequest) (<-chan ChatChunk, error)
ChatStream implements StreamingChatClient: it records the request, then replays the SAME next scripted outcome as Chat, but as chunked deltas. Text is split into a few content-delta chunks; each tool call is split into an opening chunk (ID + name + first half of arguments) and a second chunk with the rest of the arguments (exercising the agent's index-keyed accumulator); a final chunk carries usage. A scripted error is returned synchronously (connect-time), mirroring a failed open.
func (*MockLlmProvider) LastCall ¶
func (m *MockLlmProvider) LastCall() (ChatRequest, bool)
LastCall returns the most recent request and true, or a zero request and false if none have been received.
func (*MockLlmProvider) PushError ¶
func (m *MockLlmProvider) PushError(message string) *MockLlmProvider
PushError queues an error to be returned from the next Chat call.
func (*MockLlmProvider) PushResponse ¶
func (m *MockLlmProvider) PushResponse(resp ChatResponse) *MockLlmProvider
PushResponse queues a raw ChatResponse for the next Chat call.
func (*MockLlmProvider) PushText ¶
func (m *MockLlmProvider) PushText(content string) *MockLlmProvider
PushText queues a plain-text response for the next Chat call.
func (*MockLlmProvider) PushToolCall ¶
func (m *MockLlmProvider) PushToolCall(id, name, arguments string) *MockLlmProvider
PushToolCall queues a single-tool-call response for the next Chat call.
type ModelPricing ¶
ModelPricing is USD per 1,000,000 tokens, input and output.
func (ModelPricing) Cost ¶
func (p ModelPricing) Cost(u Usage) float64
Cost converts usage to USD at this pricing.
type NodeFn ¶
NodeFn transforms state into a new state. It receives a context so nodes can do async/IO work, and may return an error to abort the run.
type NoopReranker ¶
type NoopReranker struct{}
NoopReranker returns the hits unchanged — the zero-cost default.
func (NoopReranker) Rerank ¶
func (NoopReranker) Rerank(_ string, hits []KnowledgeHit) []KnowledgeHit
Rerank returns hits unchanged.
type OperatorRole ¶
type OperatorRole struct {
Name string
Kind RoleKind
Instructions string
Permissions Clearance
MaxIterations int
// Hidden roles are omitted from ListVisible (still dispatchable by name).
Hidden bool
}
OperatorRole is a named role in the cast — its kind, instructions, tool clearance, and iteration budget. Mirrors the reference engines' OperatorRole.
func NewOperatorRole ¶
func NewOperatorRole(name string, kind RoleKind, instructions string) OperatorRole
NewOperatorRole builds a role with the reference-engine defaults applied (allow-all clearance, 8 iterations).
type Reranker ¶
type Reranker interface {
Rerank(query string, hits []KnowledgeHit) []KnowledgeHit
}
Reranker reorders retrieved hits by relevance to the query.
type SmoothAgent ¶
type SmoothAgent struct {
// contains filtered or unexported fields
}
SmoothAgent is a native, in-process agent.
func NewSmoothAgent ¶
func NewSmoothAgent(client ChatClient, options AgentOptions) *SmoothAgent
NewSmoothAgent constructs an agent over an OpenAI-compatible ChatClient.
func (*SmoothAgent) Run ¶
func (a *SmoothAgent) Run(ctx context.Context, message string, history []ChatMessage) (AgentRunResponse, error)
Run executes a single turn. history is prior conversation messages (multi-turn).
func (*SmoothAgent) RunStream ¶
func (a *SmoothAgent) RunStream(ctx context.Context, message string, thread *SmoothAgentThread) (*Stream, error)
RunStream streams a single turn, delivering incremental StreamEvents on the returned channel. It drives the SAME agentic loop as Run (system/knowledge/memory build, seed messages, per-iteration compaction, cost tracking, budget early-stop, deferred-tool specs, clearance + human-gate on dispatch, checkpoint/thread persistence on exit) — but calls the model in STREAMING mode and emits events as work happens:
- a StreamText event per non-empty content delta as it streams in;
- a StreamToolCall event per requested tool call, after that iteration's model stream ends, BEFORE the call is dispatched;
- a StreamToolResult event per tool, after it finishes (in original call order even when ParallelToolCalls runs them concurrently);
- exactly one terminal StreamDone event carrying the same AgentRunResponse Run would return for the same script.
Error contract (idiomatic Go): the client must implement StreamingChatClient — if it does not, RunStream returns a nil channel and a non-nil error synchronously and runs nothing. Once the turn is running, a model-call error aborts it: the channel is closed WITHOUT a StreamDone and the error is stored, retrievable via the returned *Stream's Err() after the channel drains. So a caller ranges the channel to completion, then checks Err(); a clean turn ends with a StreamDone and Err()==nil.
NOTE: retry-with-backoff (MaxRetries/RetryBackoff) is intentionally NOT applied to the streaming model call — re-running it after a mid-stream failure would re-emit already-yielded chunks. Retry stays scoped to non-streaming Run (see callModel); this mirrors the C# RunStreamingAsync decision.
func (*SmoothAgent) RunThread ¶
func (a *SmoothAgent) RunThread(ctx context.Context, message string, thread *SmoothAgentThread) (AgentRunResponse, error)
RunThread executes a single turn carried by a SmoothAgentThread: the turn is seeded from the thread's messages, and this turn's new user + assistant (+ tool) messages are appended back to the thread before returning. The thread takes precedence over any history as the prior context. Run (single-shot/history) keeps working unchanged.
type SmoothAgentThread ¶
type SmoothAgentThread struct {
// ID is the stable id for this conversation (the key checkpoints store under).
ID string
// contains filtered or unexported fields
}
SmoothAgentThread is a conversation thread: a stable id plus the ordered non-system messages so far.
func NewThread ¶
func NewThread() *SmoothAgentThread
NewThread creates a fresh thread with an auto-generated id.
func NewThreadWithID ¶
func NewThreadWithID(id string) *SmoothAgentThread
NewThreadWithID resumes a thread under a known id (e.g. one recovered from a checkpoint). An empty id falls back to a fresh auto-generated one.
func (*SmoothAgentThread) Add ¶
func (t *SmoothAgentThread) Add(m ChatMessage)
Add appends one message, skipping any system message (rebuilt per-run).
func (*SmoothAgentThread) Extend ¶
func (t *SmoothAgentThread) Extend(msgs []ChatMessage)
Extend appends several messages, skipping any system messages.
func (*SmoothAgentThread) Len ¶
func (t *SmoothAgentThread) Len() int
Len returns the number of messages currently in the thread.
func (*SmoothAgentThread) Messages ¶
func (t *SmoothAgentThread) Messages() []ChatMessage
Messages returns the accumulated history, oldest first (no system prompt).
type Stream ¶
type Stream struct {
// contains filtered or unexported fields
}
Stream is the handle RunStream returns: range Events() to consume the turn's StreamEvents, then call Err() (after the channel drains) to see whether the turn aborted with a model error.
func (*Stream) Err ¶
Err returns the error that aborted the turn, or nil if it completed cleanly. Call it only after Events() has been fully drained (the channel closed).
func (*Stream) Events ¶
func (s *Stream) Events() <-chan StreamEvent
Events returns the channel of streamed events. It is closed when the turn ends.
type StreamEvent ¶
type StreamEvent struct {
Kind StreamEventKind
Text string // StreamText
Name string // StreamToolCall / StreamToolResult
Arguments string // StreamToolCall
Result string // StreamToolResult
Response AgentRunResponse // StreamDone
}
StreamEvent is one event from RunStream. The Kind field selects which payload fields are populated, mirroring the C# RunStreamingAsync update sequence and the Rust reference engine's event stream:
- StreamText: Text holds the content delta.
- StreamToolCall: Name + Arguments hold the requested call.
- StreamToolResult: Name + Result hold a finished tool's result.
- StreamDone: Response holds the final AgentRunResponse (the same value Run would return for the same script). Exactly one StreamDone is emitted, last, UNLESS the turn ends in an error (see RunStream's error contract).
type StreamEventKind ¶
type StreamEventKind int
StreamEventKind tags a StreamEvent.
const ( // StreamText is an incremental assistant content delta as it streams in. StreamText StreamEventKind = iota // StreamToolCall is a tool call the model requested, emitted once before dispatch. StreamToolCall // StreamToolResult is a tool's result, emitted after it finishes. StreamToolResult // StreamDone is the single terminal event, carrying the final AgentRunResponse. StreamDone )
type StreamingChatClient ¶
type StreamingChatClient interface {
ChatClient
ChatStream(ctx context.Context, req ChatRequest) (<-chan ChatChunk, error)
}
StreamingChatClient is the OPTIONAL streaming surface. A ChatClient that also implements it can drive RunStream; the GatewayClient and MockLlmProvider both do. ChatStream opens a streaming model call and returns a receive-only channel of chunks. The channel is closed when the stream ends; any error is delivered via the returned error (for connect-time failures) or — for a mid-stream failure — stored and reported as documented by the implementation. Production wires this to the OpenAI `create(..., stream=True)` surface.
type Tool ¶
type Tool interface {
Name() string
Description() string
Parameters() map[string]any
Execute(ctx context.Context, args map[string]any) (string, error)
}
Tool is a callable the agent may invoke.
func DelegateTool ¶
func DelegateTool(name, description string, child *SmoothAgent) Tool
DelegateTool builds a Tool that delegates a subtask to a child SmoothAgent.
A sub-agent is just a tool backed by another agent: the model calls this tool with a "task" argument, the child agent runs that task, and the child's final reply becomes the tool result — composing with the existing tool loop, no special wiring. The child can have its own instructions, tools, knowledge, etc.
type ToolCallDelta ¶
type ToolCallDelta struct {
Index int // which tool call this fragment belongs to
ID string // set when the call first opens ("" in later fragments)
Name string // set when the call first opens ("" in later fragments)
ArgsFragment string // a fragment of the JSON arguments to append
}
ToolCallDelta is one tool-call fragment within a streamed chunk.
type ToolSearch ¶
type ToolSearch struct {
// contains filtered or unexported fields
}
ToolSearch drives deferred-tool promotion for one agent run. It implements Tool so the agent can advertise + dispatch it like any other tool. It holds the deferred tools (by name) and the mutable set of promoted names; the agent consults PromotedTools each iteration to decide which deferred schemas are now visible/dispatchable.
func NewToolSearch ¶
func NewToolSearch(deferred []Tool) *ToolSearch
NewToolSearch builds a ToolSearch over the given deferred tools.
func (*ToolSearch) Description ¶
func (s *ToolSearch) Description() string
func (*ToolSearch) Execute ¶
Execute fuzzy-matches the query, promotes matches, and returns their schemas as JSON.
func (*ToolSearch) HasDeferred ¶
func (s *ToolSearch) HasDeferred() bool
HasDeferred reports whether any tool was registered deferred (the meta-tool is advertised only then).
func (*ToolSearch) IsPromoted ¶
func (s *ToolSearch) IsPromoted(name string) bool
IsPromoted reports whether a deferred tool has been promoted and is now dispatchable.
func (*ToolSearch) Name ¶
func (s *ToolSearch) Name() string
func (*ToolSearch) Parameters ¶
func (s *ToolSearch) Parameters() map[string]any
func (*ToolSearch) Promote ¶
func (s *ToolSearch) Promote(name string) bool
Promote marks a deferred tool promoted. Returns false if no such deferred tool.
func (*ToolSearch) PromotedTools ¶
func (s *ToolSearch) PromotedTools() []Tool
PromotedTools returns the deferred tools that have been promoted — their schemas join the visible set. Returned in registration order for determinism.
func (*ToolSearch) ToolByName ¶
func (s *ToolSearch) ToolByName(name string) (Tool, bool)
ToolByName resolves a promoted deferred tool for dispatch. Unpromoted deferred tools are invisible (returns nil, false).
type VectorKnowledge ¶
type VectorKnowledge struct {
// contains filtered or unexported fields
}
VectorKnowledge is an embedding-backed knowledge store with cosine retrieval.
func NewVectorKnowledge ¶
func NewVectorKnowledge(embedder Embedder) *VectorKnowledge
NewVectorKnowledge constructs a store with the given embedder (default HashEmbedder).
func (*VectorKnowledge) Ingest ¶
func (v *VectorKnowledge) Ingest(content, source string)
Ingest embeds and stores a document.
func (*VectorKnowledge) Query ¶
func (v *VectorKnowledge) Query(query string, topK int) []KnowledgeHit
Query embeds the query and returns up to topK docs by cosine similarity.
type Workflow ¶
type Workflow[S any] struct { // contains filtered or unexported fields }
Workflow is a typed workflow graph: named nodes connected by static/conditional edges. Build with AddNode, AddEdge / AddConditionalEdge, SetEntry, and SetEnd; the builder methods return the receiver so they chain. Run executes the graph.
func NewWorkflow ¶
NewWorkflow creates an empty workflow with the given max-steps cap (use a value <= 0 for the default of 100).
func (*Workflow[S]) AddConditionalEdge ¶
AddConditionalEdge adds a conditional edge whose router picks the next node at runtime. The router returns the target node name, or END to terminate.
func (*Workflow[S]) Run ¶
Run executes the workflow from the entry node, returning the final state.
It returns an error if no entry node was set, a referenced node does not exist, a node fails, or the maxSteps cap is exceeded (e.g. an unbroken cycle).
