Skip to main content

Agents

The Chronos agent is the central abstraction for building AI-powered applications. Agents combine a language model, tools, memory, knowledge, and optional graph-based workflows into a single, configurable unit.

Agent Struct

The Agent struct holds all configuration and runtime components:

FieldTypeDescription
IDstringUnique identifier for the agent
NamestringHuman-readable display name
DescriptionstringOptional description of the agent's purpose
UserIDstringOptional user scope for multi-tenant scenarios
Modelmodel.ProviderLLM backend for chat completions
Tools*tool.RegistryRegistered tools for function calling
Skills*skill.RegistryReusable skill definitions
Memory*memory.StoreShort-term and long-term memory store
Storagestorage.StoragePersistence for sessions, events, checkpoints
Graph*graph.CompiledGraphDurable workflow graph (optional)
Knowledgeknowledge.KnowledgeRAG knowledge base (optional)
MemoryManager*memory.ManagerLLM-powered memory extraction (optional)
Hookshooks.ChainBefore/after middleware for execution events
Guardrails*guardrails.EngineInput and output validation rules
SessionStatemap[string]anyPersistent cross-turn state
OutputSchemamap[string]anyJSON Schema for structured output
NumHistoryRunsintNumber of past runs to inject into context
ContextCfgContextConfigContext window and summarization settings
SystemPromptstringBase system prompt
Instructions[]stringAdditional system instructions
SubAgents[]*AgentChild agents for multi-agent orchestration
Capabilities[]stringAdvertised capabilities for the protocol bus

Builder API

Use agent.New(id, name) to create a builder. All methods return *Builder for chaining.

Core Configuration

a, err := agent.New("my-agent", "My Agent").
Description("A helpful assistant for technical questions").
WithUserID("user-123").
WithModel(model.NewOpenAI(apiKey)).
WithStorage(store).
WithMemory(memoryStore).
WithKnowledge(kb).
WithMemoryManager(mgr).
WithOutputSchema(schema).
WithHistoryRuns(3).
WithContextConfig(agent.ContextConfig{
MaxContextTokens: 128000,
SummarizeThreshold: 0.8,
PreserveRecentTurns: 5,
}).
WithSystemPrompt("You are a senior engineer.").
AddInstruction("Always cite sources when possible.").
AddCapability("chat").
Build()

Builder Methods

MethodDescription
Description(d string)Set agent description
WithUserID(id string)Set user scope
WithModel(p model.Provider)Set LLM provider
WithStorage(s storage.Storage)Set persistence backend
WithMemory(m *memory.Store)Set memory store
WithKnowledge(k knowledge.Knowledge)Set RAG knowledge base
WithMemoryManager(m *memory.Manager)Set LLM-powered memory manager
WithOutputSchema(s map[string]any)Set JSON Schema for structured output
WithHistoryRuns(n int)Set number of past runs to inject
WithContextConfig(cfg ContextConfig)Set context window and summarization
WithSystemPrompt(prompt string)Set base system prompt
AddInstruction(instruction string)Append system instruction
AddCapability(capability string)Add advertised capability
AddTool(def *tool.Definition)Register a tool
AddSkill(s *skill.Skill)Register a skill
AddSubAgent(sub *Agent)Add child agent
AddHook(h hooks.Hook)Add execution hook
AddInputGuardrail(name string, g guardrails.Guardrail)Add input validation
AddOutputGuardrail(name string, g guardrails.Guardrail)Add output validation
WithGraph(g *graph.StateGraph)Set workflow graph
Build()Compile and return *Agent

Execute (Lightweight Task Execution)

Execute is the simplest way to use an agent. It takes a text task, calls the model, and returns the text response. No graph, no storage, no session management — just a model call with the agent's system prompt and configuration applied.

This is the recommended entry point for agents used in multi-agent teams.

package main

import (
"context"
"fmt"
"log"
"os"

"github.com/spawn08/chronos/engine/model"
"github.com/spawn08/chronos/sdk/agent"
)

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

a, err := agent.New("helper", "Helper Agent").
WithModel(model.NewOpenAI(os.Getenv("OPENAI_API_KEY"))).
WithSystemPrompt("You are a helpful assistant. Be concise.").
Build()
if err != nil {
log.Fatal(err)
}

response, err := a.Execute(ctx, "What is the capital of France?")
if err != nil {
log.Fatal(err)
}
fmt.Println(response) // "The capital of France is Paris."
}

When to use Execute vs Chat:

  • Execute returns string — use when you just need the text response (team orchestration, simple tasks).
  • Chat returns *model.ChatResponse — use when you need the full response object (usage stats, tool calls, stop reason).

Chat (Single Turn)

Chat sends a single user message and returns the full model response. It is stateless: no session is created, and conversation history is not persisted.

The agent builds messages from: system prompt, instructions, long-term memories (via MemoryManager), relevant knowledge (via RAG), and the user message. It runs input guardrails, calls the model, handles tool calls automatically, checks output guardrails, and extracts memories.

package main

import (
"context"
"fmt"
"log"
"os"

"github.com/spawn08/chronos/engine/model"
"github.com/spawn08/chronos/sdk/agent"
)

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

a, err := agent.New("chat-agent", "Chat Agent").
WithModel(model.NewOpenAI(os.Getenv("OPENAI_API_KEY"))).
WithSystemPrompt("You are a helpful assistant.").
Build()
if err != nil {
log.Fatal(err)
}

resp, err := a.Chat(ctx, "What is the capital of France?")
if err != nil {
log.Fatal(err)
}
fmt.Println(resp.Content)
}

ChatWithSession (Multi-Turn)

ChatWithSession maintains a persistent, multi-turn conversation. Messages are stored in the event ledger. When the context window approaches its limit, older messages are automatically summarized to stay within budget.

Requires Storage and Model. The session is created on first use if it does not exist.

package main

import (
"context"
"fmt"
"log"
"os"

"github.com/spawn08/chronos/engine/model"
"github.com/spawn08/chronos/sdk/agent"
"github.com/spawn08/chronos/storage/adapters/sqlite"
)

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

store, err := sqlite.New("session.db")
if err != nil {
log.Fatal(err)
}
defer store.Close()
if err := store.Migrate(ctx); err != nil {
log.Fatal(err)
}

a, err := agent.New("session-agent", "Session Agent").
WithModel(model.NewOpenAI(os.Getenv("OPENAI_API_KEY"))).
WithStorage(store).
WithSystemPrompt("You are a helpful assistant.").
WithContextConfig(agent.ContextConfig{
SummarizeThreshold: 0.8,
PreserveRecentTurns: 5,
}).
Build()
if err != nil {
log.Fatal(err)
}

sessionID := "user-123-conv-1"

// First turn
resp1, err := a.ChatWithSession(ctx, sessionID, "My name is Alice.")
if err != nil {
log.Fatal(err)
}
fmt.Println(resp1.Content)

// Second turn (agent remembers context)
resp2, err := a.ChatWithSession(ctx, sessionID, "What is my name?")
if err != nil {
log.Fatal(err)
}
fmt.Println(resp2.Content)
}

Run (Graph or Model Execution)

Run starts an execution session. It works in two modes:

  1. Graph mode (when Graph and Storage are set): Executes a StateGraph with checkpointing. Each node receives and returns graph.State. The runner persists checkpoints to storage, enabling resume after interrupts.

  2. Model-only mode (when only Model is set): Falls back to Execute — extracts a "message" key from the input, calls the model, and returns the result as a completed RunState. This is how lightweight agents work in multi-agent teams.

Graph mode requires Graph and Storage. Model-only mode requires just Model.

package main

import (
"context"
"fmt"
"log"

"github.com/spawn08/chronos/engine/graph"
"github.com/spawn08/chronos/sdk/agent"
"github.com/spawn08/chronos/storage/adapters/sqlite"
)

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

store, err := sqlite.New("run.db")
if err != nil {
log.Fatal(err)
}
defer store.Close()
if err := store.Migrate(ctx); err != nil {
log.Fatal(err)
}

g := graph.New("workflow").
AddNode("greet", func(_ context.Context, s graph.State) (graph.State, error) {
s["greeting"] = fmt.Sprintf("Hello, %s!", s["user"])
return s, nil
}).
AddNode("classify", func(_ context.Context, s graph.State) (graph.State, error) {
s["intent"] = "general_question"
return s, nil
}).
AddNode("respond", func(_ context.Context, s graph.State) (graph.State, error) {
s["response"] = fmt.Sprintf("Intent: %s. How can I help?", s["intent"])
return s, nil
}).
SetEntryPoint("greet").
AddEdge("greet", "classify").
AddEdge("classify", "respond").
SetFinishPoint("respond")

a, err := agent.New("run-agent", "Run Agent").
WithStorage(store).
WithGraph(g).
Build()
if err != nil {
log.Fatal(err)
}

result, err := a.Run(ctx, map[string]any{"user": "World"})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Result: %v\n", result.State)
}

Resume (Continue from Checkpoint)

Resume continues a paused session from its last checkpoint. Use this when a graph contains an interrupt node that requires human approval, or when execution was stopped for any reason.

result, err := a.Resume(ctx, sessionID)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Resumed result: %v\n", result.State)

ContextConfig

ContextConfig controls context window management and automatic summarization in ChatWithSession:

FieldTypeDescription
MaxContextTokensintOverride model default; 0 = use model default
SummarizeThresholdfloat64Fraction of context window to trigger summarization (default 0.8)
PreserveRecentTurnsintNumber of recent user/assistant pairs to keep (default 5)