toroid

package module
v0.0.0-...-b72bfc8 Latest Latest
Warning

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

Go to latest
Published: Apr 22, 2026 License: MIT Imports: 27 Imported by: 0

README

toroid-kernel

toroid-kernel is a Go package for running tool-using LLM agents with persistent traces, resumable history, model-cost accounting, and a built-in tool registry.

Features

  • Agent kernel built on charm.land/fantasy
  • Provider selection from provider/model IDs
  • Session persistence with bbolt and SQLite-backed task state
  • Conversation compaction and history reconstruction
  • Built-in filesystem, shell, search, notification, and subagent tools

Install

go get github.com/yashbonde/toroid-kernel

Requires Go 1.26.1 or newer.

Quick Start

package main

import (
	"context"
	"fmt"
	"os"

	toroid "github.com/yashbonde/toroid-kernel"
)

func main() {
	ctx := context.Background()

	kernel, err := toroid.NewKernel(ctx, toroid.Config{
		Model:   "google/gemini-3-flash-preview",
		APIKey:  os.Getenv("GEMINI_TOKEN"),
		WorkDir: ".",
		Save:    true,
	})
	if err != nil {
		panic(err)
	}

	out, usage, err := kernel.Run(ctx, "Summarize the repository and point out release blockers.")
	if err != nil {
		panic(err)
	}

	fmt.Println(out)
	fmt.Printf("usage sessions: %d\n", len(usage.Tokens))
}

Run The Example

Set GEMINI_TOKEN, then run the example program from the repository root.

export GEMINI_TOKEN=your_api_key
go run ./examples -prompt 'Reply with the word OK and nothing else.'

You can also use the built-in demo flows:

go run ./examples -sequence
go run ./examples -block

Provider Examples

toroid currently supports google, anthropic, and openai model prefixes.

Google
kernel, err := toroid.NewKernel(ctx, toroid.Config{
	Model:   "google/gemini-3-flash-preview",
	APIKey:  os.Getenv("GEMINI_TOKEN"),
	WorkDir: ".",
})
Anthropic
kernel, err := toroid.NewKernel(ctx, toroid.Config{
	Model:   "anthropic/claude-sonnet-4-5",
	APIKey:  os.Getenv("ANTHROPIC_API_KEY"),
	WorkDir: ".",
})
OpenAI
kernel, err := toroid.NewKernel(ctx, toroid.Config{
	Model:   "openai/gpt-5",
	APIKey:  os.Getenv("OPENAI_API_KEY"),
	WorkDir: ".",
})

Each provider uses the same toroid.Config shape. The only required changes are the Model prefix and the API key you pass in.

Release Notes

  • The module path is github.com/yashbonde/toroid-kernel.
  • Embedded prompts live in prompts/ and pricing assets live in assets/.
  • Runtime state is stored under ~/.swarmbuddy/.

Status

This repository currently ships as a library package. A license file is not included yet, so choose and add one before publishing a public release.

Documentation

Overview

Package toroid provides an agent kernel with persistence, tool execution, usage accounting, and session history reconstruction.

Index

Constants

View Source
const (
	TraceLogInfo    = "info"
	TraceLogWarning = "warning"
	TraceLogError   = "error"
)

Variables

This section is empty.

Functions

func ApplyDefaults

func ApplyDefaults(cfg any)

func BboltPath

func BboltPath() (string, error)

BboltPath returns ~/.swarmbuddy/traces.bbolt.db

func CalculateCost

func CalculateCost(modelID string, usage Usage, curr string) (float64, error)

CalculateCost computes the total cost for a usage breakdown using default pricing.

func ConfigPath

func ConfigPath() (string, error)

ConfigPath returns ~/.swarmbuddy/config.json

func DeleteSession

func DeleteSession(id string) error

DeleteSession removes all data associated with a trace ID.

func LogDebug

func LogDebug(msg string, args ...any)

func LogError

func LogError(msg string, args ...any)

func LogInfo

func LogInfo(msg string, args ...any)

func NewProviderFromLLMId

func NewProviderFromLLMId(llmID, apiKey string) (fantasy.Provider, error)

NewProviderFromLLMId creates a fantasy.Provider from an LLM ID of the form "provider/model-name" (matching the keys in pricing.json). Supported prefixes: google, anthropic, openai.

func NewSessionID

func NewSessionID() string

NewSessionID generates a monotonic, human-readable session ID. Format: <unix_seconds>-<4char_random>

func PrettyPrintHistory

func PrettyPrintHistory(kernel *Kernel)

func ReconstructHistory

func ReconstructHistory(traceID, spanID, systemPrompt string) ([]fantasy.Message, error)

ReconstructHistory rebuilds a []fantasy.Message from the events stored in bbolt for a trace. Only events after the last compaction are replayed, so the returned history is exactly what the kernel would have in memory for a resumed session.

systemPrompt is prepended as a system message when non-empty. If spanID is non-empty only events from that span are used; otherwise events from all spans under the trace are combined in span order (useful for subagent traces).

func RunnerDir

func RunnerDir(cwd, traceID string) (string, error)

RunnerDir returns {cwd}/.swarmbuddy_tmp/{traceID}/, creating it if needed.

func SqlitePath

func SqlitePath() (string, error)

SqlitePath returns ~/.swarmbuddy/sql.db

func StorageDir

func StorageDir() (string, error)

StorageDir returns ~/.swarmbuddy/storage/, creating it if needed.

func TracesDir

func TracesDir() (string, error)

TracesDir returns ~/.swarmbuddy/traces/, creating it if needed.

Types

type AssistantTurnPayload

type AssistantTurnPayload struct {
	Messages json.RawMessage `json:"messages"`
}

AssistantTurnPayload is attached to EventAssistantTurn. Messages is a JSON-serialized []fantasy.Message containing the full structured content blocks (thinking, text, tool_use, tool_result) from all steps of the turn.

type CompactPayload

type CompactPayload struct {
	MessageCount int `json:"message_count"`
	TokenCount   int `json:"token_count"`
}

type CompactSummaryPayload

type CompactSummaryPayload struct {
	Summary string `json:"summary"`
}

CompactSummaryPayload is attached to EventPostCompact. It contains the LLM-generated summary of the conversation before the context was reset.

type Config

type Config struct {
	Provider       fantasy.Provider `json:"provider,omitempty" description:"llm provider"`
	Model          string           `json:"model" description:"llm model name" default:"gemini-3-flash-preview"`
	APIKey         string           `json:"api_key,omitempty" description:"API key for the provider"`
	SessionID      string           `json:"session_id,omitempty" description:"unique identifier for the session"`
	WorkDir        string           `json:"work_dir" description:"working directory" default:"current directory"`
	MaxIter        int              `json:"max_iter" description:"max tool-call iterations" default:"50"`
	Thinking       Thinking         `json:"thinking" description:"thinking budget: none | low | high" default:"none"`
	ThinkingWriter io.Writer        `json:"-"`

	// Trace/span hierarchy
	TraceID      string `json:"trace_id,omitempty"`       // inherited from parent; root sets TraceID = SessionID
	ParentSpanID string `json:"parent_span_id,omitempty"` // parent kernel's SessionID

	// Persistence
	Save bool `json:"save" description:"persist events, costs and metadata to the bbolt store" default:"false"`

	// Session management
	Resume        bool `json:"resume" description:"if true, load existing session history and continue" default:"false"`
	GenerateTitle bool `json:"generate_title" description:"if true, generate title for the session" default:"false"`

	// compaction
	CompactionBufferSize int `json:"compaction_buffer_size" description:"buffer size for history compaction" default:"30000"`
	ToolCallPrunedSize   int `json:"tool_call_prune" description:"token limit for tool call after pruning" default:"40000"`
	TotalContextSize     int `json:"total_context_size" description:"total context window size" default:"300000"`

	// logging flags
	AttachLoggerHooks *bool `json:"attach_logger_hooks,omitempty" description:"automatically attach logger hooks" default:"false"`
	ShowHistory       *bool `json:"show_history" description:"print history" default:"false"`
}

Config holds all options for creating a Kernel.

type CostEvent

type CostEvent struct {
	TS       int64   `json:"ts"` // UnixNano (from bucket key)
	TurnUSD  float64 `json:"turn_usd"`
	TotalUSD float64 `json:"total_usd"`
}

CostEvent is a single turn cost record stored under a span.

type Event

type Event struct {
	Kind      EventKind `json:"kind"`
	SessionID string    `json:"session_id"`
	TraceID   string    `json:"trace_id"`
	SpanID    string    `json:"span_id"`
	EmitTS    int64     `json:"emit_ts"` // UnixNano wall clock
	Seq       uint64    `json:"seq"`     // monotonic counter within a span
	Payload   any       `json:"payload,omitempty"`
}

type EventKind

type EventKind string
const (
	EventTraceLog           EventKind = "TraceLog" // structured log entry stored in the trace; visible in UI and readable by follow-on agents
	EventSessionStart       EventKind = "SessionStart"
	EventUserPromptSubmit   EventKind = "UserPromptSubmit"
	EventToken              EventKind = "Token"              // each streamed text chunk (display only, not stored)
	EventPermissionRequest  EventKind = "PermissionRequest"  // before a tool is called, if permission is required
	EventPreToolUse         EventKind = "PreToolUse"         // before a tool is called
	EventPostToolUse        EventKind = "PostToolUse"        // after a tool call is completed
	EventPostToolUseFailure EventKind = "PostToolUseFailure" // after a tool call fails
	EventSubagentStart      EventKind = "SubagentStart"      // before the subagent is started
	EventSubagentStop       EventKind = "SubagentStop"       // before the subagent is stopped
	EventMasterIdle         EventKind = "MasterIdle"         // after the main agent is idle
	EventNotification       EventKind = "Notification"       // before the notification is sent
	EventTaskCompleted      EventKind = "TaskCompleted"      // before the task is completed
	EventTitle              EventKind = "Title"              // fired async when session title is ready
	EventReasoning          EventKind = "Reasoning"          // streamed reasoning/thinking tokens (display only, not stored)
	EventAssistantTurn      EventKind = "AssistantTurn"      // full structured content blocks for the turn (thinking+text+tool_use)
	EventTurnCost           EventKind = "TurnCost"           // after each LLM turn, with incremental cost
	EventStop               EventKind = "Stop"               // when the agent is stopped
	EventPreCompact         EventKind = "PreCompact"         // before compacting the memory
	EventPostCompact        EventKind = "PostCompact"        // after compaction; payload contains the LLM-generated summary
	EventSessionEnd         EventKind = "SessionEnd"         // after the session ends
)

type HookFn

type HookFn func(ctx context.Context, e Event) error

type HookRegistry

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

func (*HookRegistry) Fire

func (r *HookRegistry) Fire(ctx context.Context, e Event) error

Fire runs all registered hooks for the event kind in order. A non-nil error from any hook aborts the chain and is returned.

func (*HookRegistry) On

func (r *HookRegistry) On(kind EventKind, fn HookFn)

type Kernel

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

Kernel is the agentic orchestrator powered by Fantasy.

func NewKernel

func NewKernel(ctx context.Context, cfg Config) (*Kernel, error)

NewKernel creates and wires up a new Kernel.

func (*Kernel) Compact

func (k *Kernel) Compact(ctx context.Context) error

Compact summarizes the current history and resets it.

func (*Kernel) ContextUsage

func (k *Kernel) ContextUsage() (int, int)

ContextUsage returns (used tokens, total context window size).

func (*Kernel) Fire

func (k *Kernel) Fire(ctx context.Context, kind string, payload any) error

func (*Kernel) FireTraceLog

func (k *Kernel) FireTraceLog(ctx context.Context, logType, message string) error

FireTraceLog emits an EventTraceLog with the given severity and message.

func (*Kernel) LogErr

func (k *Kernel) LogErr(msg string, args ...any)

func (*Kernel) Logf

func (k *Kernel) Logf(msg string, args ...any)

func (*Kernel) Model

func (k *Kernel) Model() string

func (*Kernel) On

func (k *Kernel) On(kind EventKind, fn HookFn)

On registers a hook for an event kind.

func (*Kernel) OnAll

func (k *Kernel) OnAll(fn HookFn)

func (*Kernel) Run

func (k *Kernel) Run(ctx context.Context, prompt string) (string, UsagePayload, error)

Run runs the agent loop and returns the final text response.

func (*Kernel) RunSubagent

func (k *Kernel) RunSubagent(ctx context.Context, task string) (string, error)

RunSubagent runs a subagent synchronously and returns its output.

func (*Kernel) RunningCostUSD

func (k *Kernel) RunningCostUSD() float64

RunningCostUSD returns the cumulative LLM cost so far in this session.

func (*Kernel) SessionID

func (k *Kernel) SessionID() string

func (*Kernel) Stream

func (k *Kernel) Stream(ctx context.Context, prompt string, w io.Writer) error

Stream runs the agent loop and streams the response to the writer.

func (*Kernel) UpdateUse

func (k *Kernel) UpdateUse(u Usage, key string)

TODO: Improve this function, idea is to use this as a single place for all usage udpates

func (*Kernel) WorkDir

func (k *Kernel) WorkDir() string

Implement tools.Agent interface

type ModelPricing

type ModelPricing struct {
	Prompt     float64 `json:"Prompt"`
	Completion float64 `json:"Completion"`
	Reasoning  float64 `json:"Reasoning"`
	CacheRead  float64 `json:"CacheRead"`
	CacheWrite float64 `json:"CacheWrite"`
}

ModelPricing defines the cost per token for an LLM.

func GetModelPricing

func GetModelPricing(modelID string) (ModelPricing, error)

GetModelPricing returns the singleton pricing instance.

type NotificationPayload

type NotificationPayload struct {
	Title   string `json:"title"`
	Message string `json:"message"`
}

type PermissionPayload

type PermissionPayload struct {
	ToolName string         `json:"tool_name"`
	Args     map[string]any `json:"args"`
	Verdict  string         `json:"verdict"` // "allow" | "deny"
}

type ReasoningPayload

type ReasoningPayload struct {
	Text string `json:"text"`
}

type SessionInfo

type SessionInfo struct {
	ID          string // trace ID (root span ID)
	Title       string
	StartedAt   int64   // UnixNano
	DurationNs  int64   // last cost event ts - started_at (wall time)
	AgentTimeNs int64   // wall time minus total tool execution time
	TotalUSD    float64 // sum of all turn_usd across all spans
}

SessionInfo holds metadata for listing traces/sessions.

func ListSessions

func ListSessions() ([]SessionInfo, error)

ListSessions returns all traces sorted newest first.

func (SessionInfo) AgentTimeFmt

func (s SessionInfo) AgentTimeFmt() string

func (SessionInfo) DurationFmt

func (s SessionInfo) DurationFmt() string

func (SessionInfo) StartedAtFmt

func (s SessionInfo) StartedAtFmt() string

type SpanData

type SpanData struct {
	SpanMeta
	Costs  []CostEvent `json:"costs"`
	Events []Event     `json:"events"`
}

SpanData is a span with its cost events and session events, used for visualization.

type SpanMeta

type SpanMeta struct {
	SpanID       string `json:"span_id"`
	TraceID      string `json:"trace_id"`
	ParentSpanID string `json:"parent_span_id,omitempty"`
	Model        string `json:"model,omitempty"`
	Title        string `json:"title,omitempty"`
	StartedAt    int64  `json:"started_at"` // UnixNano
	EndedAt      int64  `json:"ended_at,omitempty"`
}

SpanMeta is stored per span (kernel session, including subagents).

type StopPayload

type StopPayload struct {
	Reason string `json:"reason"`
}

type Store

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

Store wraps a bbolt database for all persistence needs.

func NewStore

func NewStore() (*Store, error)

NewStore opens (or reuses) the singleton bbolt database (~/.swarmbuddy/traces.bbolt.db).

func NewStoreReadWrite

func NewStoreReadWrite() (*Store, error)

NewStoreReadWrite is an alias for NewStore. Both CLI and server share one file. Only one process should hold the lock at a time.

func (*Store) AppendCost

func (s *Store) AppendCost(traceID, spanID string, turnUSD, totalUSD float64) error

AppendCost records a turn cost under a span's cost bucket.

func (*Store) AppendEvent

func (s *Store) AppendEvent(traceID, spanID string, event Event) error

AppendEvent records a session event under a span's event bucket.

func (*Store) LoadLastTotalUSD

func (s *Store) LoadLastTotalUSD(traceID, spanID string) float64

LoadLastTotalUSD returns the total_usd from the most recent cost entry for a span, or 0 if none.

func (*Store) LoadMemories

func (s *Store) LoadMemories(spanID string) (map[string]any, error)

LoadMemories reads the agent's persistent memory JSON blob for a span.

func (*Store) LoadTraceMeta

func (s *Store) LoadTraceMeta(traceID string) (TraceMeta, error)

LoadTraceMeta reads trace metadata by trace ID.

func (*Store) LoadTraceTotal

func (s *Store) LoadTraceTotal(traceID string) float64

LoadTraceTotal returns the sum of the last total_usd across all spans for a trace. This represents the cumulative cost of all previous runs under this trace ID.

func (*Store) SaveMemories

func (s *Store) SaveMemories(spanID string, mem map[string]any) error

SaveMemories writes the agent's persistent memory JSON blob for a span.

func (*Store) SaveSpanMeta

func (s *Store) SaveSpanMeta(meta SpanMeta) error

SaveSpanMeta writes or updates span metadata.

func (*Store) SaveTraceMeta

func (s *Store) SaveTraceMeta(meta TraceMeta) error

SaveTraceMeta writes or updates trace metadata.

func (*Store) UpdateTraceTitle

func (s *Store) UpdateTraceTitle(traceID, title string) error

UpdateTraceTitle patches only the title field of an existing TraceMeta, preserving StartedAt.

type SubagentPayload

type SubagentPayload struct {
	SessionID    string       `json:"session_id"`
	Prompt       string       `json:"prompt"`
	Output       string       `json:"output,omitempty"`
	UsagePayload UsagePayload `json:"usage,omitempty"`
}

type TaskPayload

type TaskPayload struct {
	TaskID string `json:"task_id"`
	Title  string `json:"title"`
	Status string `json:"status"`
}

type Thinking

type Thinking string

Thinking controls the model's thinking budget.

const (
	ThinkingNone Thinking = "none" // disable thinking (budget=0)
	ThinkingLow  Thinking = "low"  // ~1k tokens
	ThinkingHigh Thinking = "high" // ~8k tokens
)

type TitlePayload

type TitlePayload struct {
	Title string `json:"title"`
}

type TokenPayload

type TokenPayload struct {
	Text string `json:"text"`
}

type ToolUsePayload

type ToolUsePayload struct {
	CallID string `json:"call_id"` // tool_call_id assigned by the LLM
	Name   string `json:"name"`
	Args   string `json:"args"`
}

type ToolUseResultPayload

type ToolUseResultPayload struct {
	CallID string `json:"call_id,omitempty"` // tool_call_id linking back to the PreToolUse event
	Name   string `json:"name,omitempty"`
	Result string `json:"result,omitempty"`
	Error  string `json:"error,omitempty"`
}

type TraceData

type TraceData struct {
	Trace TraceMeta  `json:"trace"`
	Spans []SpanData `json:"spans"`
}

TraceData is the full trace for visualization.

func LoadTraceData

func LoadTraceData(traceID string) (TraceData, error)

LoadTraceData reads the full trace + all spans + costs for a given trace ID.

type TraceLogPayload

type TraceLogPayload struct {
	Type    string `json:"type"`
	Message string `json:"message"`
}

TraceLogPayload is attached to EventTraceLog.

type TraceMeta

type TraceMeta struct {
	TraceID   string `json:"trace_id"`
	Title     string `json:"title,omitempty"`
	StartedAt int64  `json:"started_at"` // UnixNano
	EndedAt   int64  `json:"ended_at,omitempty"`
}

TraceMeta is stored per trace (root kernel run).

type TurnCostPayload

type TurnCostPayload struct {
	TurnUsage    Usage   `json:"turn_usage"`     // tokens consumed in this single turn
	TurnCostUSD  float64 `json:"turn_cost_usd"`  // cost of this turn in USD
	TotalCostUSD float64 `json:"total_cost_usd"` // cumulative cost so far in USD
}

TurnCostPayload is attached to EventTurnCost, fired after each LLM turn.

type USDToLocalCurrency

type USDToLocalCurrency struct {
	Name string  `json:"name"`
	Rate float64 `json:"rate"`
	Date string  `json:"date"`
}

LLM endpoint returns cost in USD, for local currency calculation we need to create a map

func GetCurrencyMultiplier

func GetCurrencyMultiplier(curr string) (USDToLocalCurrency, error)

type Usage

type Usage struct {
	Output     int64
	Input      int64
	Reasoning  int64
	CacheRead  int64
	CacheWrite int64
	Cost       float64
}

func (*Usage) FromFantasyUsage

func (u *Usage) FromFantasyUsage(usage fantasy.Usage, model string)

type UsagePayload

type UsagePayload struct {
	Tokens map[string]Usage `json:"tokens"` // sessionID -> token breakdown
}

UsagePayload is attached to EventStop and contains the total token usage across the session and all subagents it spawned, keyed by session ID.

type UserPromptPayload

type UserPromptPayload struct {
	Prompt string `json:"prompt"`
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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