Package path: offdev/micro-agent-go/internal/core
Last updated: 2026-03-26
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.
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.
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
}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
}type Memory struct {
Key string
Content string
Embedding []float32
Tags []string
Timestamp time.Time
AgentID string
}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
}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.
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).
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
}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.Run(ctx, conv, streamConsumer) implements the loop. Pass nil for streamConsumer for non-streaming.
- Prepend system message (if
Instructionsis set and first message is not already system) CallbackChain.BeforeAgentLoop- Per iteration:
a.
CallbackChain.BeforeLLMCall(may mutate messages) b. LLM turn: up toemptyResponseMaxRetries + 1attempts. If the model returns empty content and no tool calls, the loop injects a synthetic user nudge (emptyResponseRetryNudge) and retries; after exhausting retries,Runreturns an error. c. WhenstreamConsumeris non-nil:Provider.Stream, invokestreamConsumerfor each delta, useDelta.Final(or fallback toCompleteifFinalis nil). Otherwise:Provider.Completed.CallbackChain.AfterLLMCalle. Append assistant message (final reply only; thinking is not persisted) f. If no tool calls → break g. Dispatch tool calls concurrently (sync.WaitGroup), applyingBeforeToolExecution/AfterToolExecutionper call h. Append tool results CallbackChain.AfterAgentLoop
Agent is safe for concurrent use across multiple goroutines (each with its own Conversation).
buildToolDefsallocates once perRuncall, not per iteration.toolMapis a localmap[string]Toolbuilt at the start ofRun; no lock contention.dispatchToolsuses a singlesync.WaitGroup; goroutines are bounded by the number of tool calls in a single model response (typically ≤ 10).