Skip to content

Latest commit

 

History

History
219 lines (172 loc) · 6.51 KB

File metadata and controls

219 lines (172 loc) · 6.51 KB

core — Fundamental Types and Interfaces

Package path: offdev/micro-agent-go/internal/core
Last updated: 2026-03-26


Overview

The core package is the dependency root of the module. Every other package imports it; core imports no internal project packages. It defines the agent loop, all interface contracts, and the shared data types exchanged across packages.


Types

Message / Conversation

type Role string // "system" | "user" | "assistant" | "tool"

type MessageImage struct {
    MIME string
    Data []byte
}

type Message struct {
    Role       Role
    Content    string
    Images     []MessageImage // optional vision input on user messages (in-memory session only)
    ToolCalls  []ToolCall     // set on assistant messages
    ToolCallID string         // set on tool-result messages
    Name       string         // tool name for tool-result messages
}

type Conversation struct {
    ID       string
    Messages []Message
}

Conversation.Append appends a message in place. Conversation.Clone returns a shallow copy with an independent message slice.

ToolCall / ToolResult / ToolDef

type ToolCall struct {
    ID     string
    Name   string
    Params json.RawMessage // raw JSON object matching the tool schema
}

type ToolResult struct {
    CallID  string
    Name    string
    Content string
    Err     error
}

type ToolDef struct {
    Name        string
    Description string
    Parameters  json.RawMessage // JSON Schema object
}

ModelConfig

type ModelConfig struct {
    Provider          LLMProvider
    Name              string
    MaxTokens         int
    Temperature       float32
    TopP              float32   // nucleus sampling; 0 = provider default
    TopK              int       // top-K sampling; 0 = provider default
    MinP              float32   // min token probability; 0 = provider default
    PresencePenalty   float32   // 0 = provider default
    RepetitionPenalty float32   // 0 = provider default
    ThinkingEnabled   *bool     // nil = omit from API / provider default
}

Memory

type Memory struct {
    Key       string
    Content   string
    Embedding []float32
    Tags      []string
    Timestamp time.Time
    AgentID   string
}

Response / Delta / ModelInfo

type Response struct {
    Content      string
    ToolCalls    []ToolCall
    InputTokens  int
    OutputTokens int
}

type Delta struct {
    Content  string    // incremental text (thinking or main response)
    Done     bool      // true on final chunk
    Thinking bool      // true when Content is reasoning (display only; not persisted)
    Final    *Response // set on Done chunk with accumulated content + tool_calls
}

type ModelInfo struct {
    ID   string
    Name string
}

Interfaces

Tool

type Tool interface {
    Name() string
    Description() string
    Schema() json.RawMessage
    Execute(ctx context.Context, params json.RawMessage) (string, error)
}

All Execute implementations must be safe for concurrent use; the agent loop dispatches independent tool calls in parallel.

LLMProvider

type LLMProvider interface {
    Complete(ctx context.Context, req *CompletionRequest) (*Response, error)
    Stream(ctx context.Context, req *CompletionRequest) (<-chan Delta, error)
    Models(ctx context.Context) ([]ModelInfo, error)
}

Stream returns a channel that the caller must fully drain. When the provider supports thinking/reasoning (e.g. llama-server with reasoning_content), deltas may have Thinking true; the final delta has Done true and should have Final set with the accumulated response so the agent can append the assistant message without a separate Complete call.

CompletionRequest includes ThinkingEnabled *bool (wired to the provider when non-nil).

MemoryStore

type SearchOptions struct {
    TagsInclude []string  // optional: restrict to entries with at least one of these tags
}

type MemoryStore interface {
    Save(ctx context.Context, key string, value Memory) error
    Get(ctx context.Context, key string) (*Memory, error)
    Search(ctx context.Context, query string, topK int, opts *SearchOptions) ([]Memory, error)
    Delete(ctx context.Context, key string) error
}

Callback / CallbackChain

type Callback interface {
    BeforeAgentLoop(ctx context.Context, conv *Conversation) error
    AfterAgentLoop(ctx context.Context, conv *Conversation) error
    BeforeLLMCall(ctx context.Context, messages []Message) ([]Message, error)
    AfterLLMCall(ctx context.Context, response *Response) error
    BeforeToolExecution(ctx context.Context, call ToolCall) (ToolCall, error)
    AfterToolExecution(ctx context.Context, call ToolCall, result ToolResult) error
}

CallbackChain is []Callback. A nil slice is valid. BeforeLLMCall chains messages through each callback in sequence. Errors from AfterToolExecution are ignored by the agent loop (the hook is best-effort logging). Use NoopCallback as a base when only some hooks are needed.


Agent Loop

Agent.Run(ctx, conv, streamConsumer) implements the loop. Pass nil for streamConsumer for non-streaming.

  1. Prepend system message (if Instructions is set and first message is not already system)
  2. CallbackChain.BeforeAgentLoop
  3. Per iteration: a. CallbackChain.BeforeLLMCall (may mutate messages) b. LLM turn: up to emptyResponseMaxRetries + 1 attempts. If the model returns empty content and no tool calls, the loop injects a synthetic user nudge (emptyResponseRetryNudge) and retries; after exhausting retries, Run returns an error. c. When streamConsumer is non-nil: Provider.Stream, invoke streamConsumer for each delta, use Delta.Final (or fallback to Complete if Final is nil). Otherwise: Provider.Complete d. CallbackChain.AfterLLMCall e. Append assistant message (final reply only; thinking is not persisted) f. If no tool calls → break g. Dispatch tool calls concurrently (sync.WaitGroup), applying BeforeToolExecution / AfterToolExecution per call h. Append tool results
  4. CallbackChain.AfterAgentLoop

Agent is safe for concurrent use across multiple goroutines (each with its own Conversation).


Performance Notes

  • buildToolDefs allocates once per Run call, not per iteration.
  • toolMap is a local map[string]Tool built at the start of Run; no lock contention.
  • dispatchTools uses a single sync.WaitGroup; goroutines are bounded by the number of tool calls in a single model response (typically ≤ 10).