Skip to content

Commit e879f16

Browse files
committed
docs: add Programmatic API doc (docs/API.md)
Documents the full Go library surface: - odek.Tool interface with example custom tool - odek.Config with all fields - odek.New() constructor behavior - odek.Agent methods (Run, RunWithMessages, Close, Memory, token tracking) - Model profiles (KnownProfiles, LookupProfile, ProfileLabel) - AGENTS.md project file - Complete runnable example - Import path and public symbol table Links added to README, landing page doc-links, and DEVELOPMENT.md source layout.
1 parent b8f0924 commit e879f16

4 files changed

Lines changed: 372 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ odek repl
138138
|-----|--------|
139139
| [CLI Reference](docs/CLI.md) | All commands, subcommands, flags, error codes |
140140
| [Configuration](docs/CONFIG.md) | Config files, env vars, priority chain, all sections |
141+
| [Programmatic API](docs/API.md) | Go library: Tool interface, Config, Agent, Run, model profiles |
141142
| [Providers & Models](docs/PROVIDERS.md) | Supported providers, thinking config, context windows |
142143
| [Memory](docs/MEMORY.md) | Three-tier design, go-vector merge-on-write, `memory` tool |
143144
| [Sessions](docs/SESSIONS.md) | Multi-turn conversations, save/resume/trim/cleanup |

docs/API.md

Lines changed: 369 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
1+
# Programmatic API
2+
3+
odek is designed to be used both as a CLI tool and as a **Go library**. Import `github.com/BackendStack21/kode` and build your own agents, tools, and workflows — all from the same binary.
4+
5+
```go
6+
import "github.com/BackendStack21/kode"
7+
```
8+
9+
---
10+
11+
## `odek.Tool` Interface
12+
13+
The only extension point. Tools are plain Go structs with four methods:
14+
15+
```go
16+
type Tool interface {
17+
Name() string
18+
Description() string
19+
Schema() any // JSON Schema object describing parameters
20+
Call(args string) (string, error)
21+
}
22+
```
23+
24+
- **Name** — unique identifier used by the LLM to invoke the tool. Lowercase, underscore-separated (e.g. `read_file`, `delegate_tasks`).
25+
- **Description** — natural-language description of what the tool does. The LLM reads this to decide when to call it.
26+
- **Schema** — a JSON Schema object (`map[string]any`) defining the tool's parameters. The LLM uses this to construct valid arguments.
27+
- **Call** — the implementation. Receives JSON-marshalled arguments, returns a string result (rendered to the LLM in the next iteration).
28+
29+
### Example: Custom Tool
30+
31+
```go
32+
type fileStatsTool struct{}
33+
34+
func (t *fileStatsTool) Name() string { return "file_stats" }
35+
36+
func (t *fileStatsTool) Description() string {
37+
return "Get file statistics: line count, word count, character count."
38+
}
39+
40+
func (t *fileStatsTool) Schema() any {
41+
return map[string]any{
42+
"type": "object",
43+
"properties": map[string]any{
44+
"path": map[string]any{
45+
"type": "string",
46+
"description": "Path to the file",
47+
},
48+
},
49+
"required": []string{"path"},
50+
}
51+
}
52+
53+
func (t *fileStatsTool) Call(args string) (string, error) {
54+
var params struct {
55+
Path string `json:"path"`
56+
}
57+
if err := json.Unmarshal([]byte(args), &params); err != nil {
58+
return "", err
59+
}
60+
data, err := os.ReadFile(params.Path)
61+
if err != nil {
62+
return fmt.Sprintf("error: %v", err), nil
63+
}
64+
lines := strings.Count(string(data), "\n")
65+
words := len(strings.Fields(string(data)))
66+
return fmt.Sprintf("lines: %d, words: %d, chars: %d", lines, words, len(data)), nil
67+
}
68+
```
69+
70+
---
71+
72+
## `odek.Config` Struct
73+
74+
All configuration for an `odek.Agent`. Fields with zero values fall back to sensible defaults.
75+
76+
```go
77+
type Config struct {
78+
// Model identifier (e.g. "deepseek-v4-flash", "gpt-4o").
79+
// Default: "deepseek-chat"
80+
Model string
81+
82+
// OpenAI-compatible API endpoint.
83+
// Default: "https://api.deepseek.com/v1"
84+
BaseURL string
85+
86+
// API key for the LLM provider.
87+
// Falls back to DEEPSEEK_API_KEY, then OPENAI_API_KEY.
88+
APIKey string
89+
90+
// Thinking depth (provider-specific):
91+
// Deepseek: "enabled" | "disabled"
92+
// OpenAI o-series: "low" | "medium" | "high"
93+
// Empty = model profile default (or provider default if profile has none).
94+
Thinking string
95+
96+
// Tools available to the agent.
97+
Tools []Tool
98+
99+
// Maximum think→act cycles (default: 90).
100+
MaxIterations int
101+
102+
// System prompt injected at the start of every run.
103+
// If AGENTS.md exists in the working directory, its content is
104+
// appended automatically with a "Project Instructions" header.
105+
// Set NoProjectFile to true to skip this.
106+
SystemMessage string
107+
108+
// Disable automatic AGENTS.md loading.
109+
NoProjectFile bool
110+
111+
// Cleanup function called by Close() (e.g., destroy sandbox container).
112+
// Set by the CLI when --sandbox is active. When nil, Close() is a no-op.
113+
SandboxCleanup func() error
114+
115+
// Terminal renderer for colored output. When nil, agent runs silently.
116+
Renderer *render.Renderer
117+
118+
// Skills config. When nil, skills are disabled.
119+
Skills *skills.SkillsConfig
120+
121+
// Pre-loaded skill manager. When nil, New() auto-loads from
122+
// ~/.odek/skills/ and ./.odek/skills/.
123+
SkillManager *skills.SkillManager
124+
125+
// Directory for persistent memory storage.
126+
// Default: ~/.odek/memory/
127+
MemoryDir string
128+
129+
// Memory system configuration (facts, buffer, episodes).
130+
// Default: memory.DefaultMemoryConfig()
131+
MemoryConfig memory.MemoryConfig
132+
}
133+
```
134+
135+
---
136+
137+
## Agent Constructor
138+
139+
```go
140+
func New(cfg Config) (*Agent, error)
141+
```
142+
143+
Creates a new agent with the given configuration. The constructor:
144+
145+
1. Applies defaults for missing fields (`Model`, `BaseURL`, `MaxIterations`)
146+
2. Resolves the API key (from config → `DEEPSEEK_API_KEY` → `OPENAI_API_KEY`)
147+
3. Applies model profile defaults (thinking depth, timeout, context window)
148+
4. Builds the internal tool registry from `cfg.Tools`
149+
5. Loads `AGENTS.md` from the working directory (unless `NoProjectFile` is set)
150+
6. Loads skills and injects auto-load skills into the system message
151+
7. Creates the memory manager and injects fact/buffer context into the system prompt
152+
8. Wires up lazy skill loading via the trigger-based trie index
153+
154+
Returns an error if no API key is found.
155+
156+
---
157+
158+
## `odek.Agent` Methods
159+
160+
### Run — Single-shot Task
161+
162+
```go
163+
func (a *Agent) Run(ctx context.Context, task string) (string, error)
164+
```
165+
166+
Executes the agent loop for a single task and returns the final answer. Best for one-off questions where conversation history isn't needed.
167+
168+
```go
169+
result, err := agent.Run(ctx, "Refactor this module")
170+
```
171+
172+
### RunWithMessages — Multi-Turn / Sessions
173+
174+
```go
175+
func (a *Agent) RunWithMessages(ctx context.Context, messages []llm.Message) (string, []llm.Message, error)
176+
```
177+
178+
Executes the agent loop starting from a pre-built message history. Returns the final answer plus the complete updated message history. Use this for multi-turn conversations where you load a prior session, append the new user message, and persist the updated history afterwards.
179+
180+
```go
181+
messages := sess.GetMessages()
182+
messages = append(messages, llm.Message{Role: "user", Content: input})
183+
answer, allMessages, err := agent.RunWithMessages(ctx, messages)
184+
store.Append(sess.ID, allMessages[origLen:])
185+
```
186+
187+
### Token Tracking
188+
189+
```go
190+
func (a *Agent) TotalInputTokens() int
191+
func (a *Agent) TotalOutputTokens() int
192+
```
193+
194+
Returns cumulative token counts from the most recent `Run` / `RunWithMessages` call. Use after each turn for session-level token economics.
195+
196+
### Memory Access
197+
198+
```go
199+
func (a *Agent) Memory() *memory.MemoryManager
200+
```
201+
202+
Returns the agent's memory manager for direct manipulation (buffer appends, episode extraction). Returns `nil` if memory is disabled. Used by the CLI layer after each turn and on session end.
203+
204+
### Close
205+
206+
```go
207+
func (a *Agent) Close() error
208+
```
209+
210+
Cleans up resources. If a `SandboxCleanup` function was set, it's called here (e.g., destroys the Docker sandbox container). **Always call `Close()` when done.**
211+
212+
```go
213+
defer agent.Close()
214+
```
215+
216+
---
217+
218+
## Model Profiles
219+
220+
Profiles provide per-model defaults for thinking depth, timeout, and context window limits. They are matched by **longest model-name prefix** — a profile for `deepseek-v4-flash` matches before a broader `deepseek-` profile.
221+
222+
### Built-in Profiles
223+
224+
| Prefix | Label | Default Thinking | Timeout | Max Context |
225+
|--------|-------|-----------------|---------|-------------|
226+
| `deepseek-v4-pro` | DeepSeek v4 Pro | enabled | 180s | 1,000,000 |
227+
| `deepseek-v4-flash` | DeepSeek v4 Flash | — | 90s | 131,072 |
228+
| `deepseek-` | DeepSeek (generic) | — | 120s | 131,072 |
229+
230+
### Adding a Profile
231+
232+
Append an entry to `KnownProfiles` — no changes to the LLM client, loop engine, or CLI needed:
233+
234+
```go
235+
var KnownProfiles = []struct {
236+
Prefix string
237+
Profile ModelProfile
238+
}{
239+
{
240+
Prefix: "gpt-4o",
241+
Profile: ModelProfile{
242+
Label: "GPT-4o",
243+
Timeout: 120,
244+
MaxContext: 128_000,
245+
},
246+
},
247+
// ...
248+
}
249+
```
250+
251+
### Lookup Functions
252+
253+
```go
254+
func LookupProfile(model string) *ModelProfile
255+
func ProfileLabel(model string) string
256+
```
257+
258+
`LookupProfile` returns the best-matching profile (longest prefix). `ProfileLabel` returns the human-readable label or the model name if no profile matches.
259+
260+
---
261+
262+
## Project File (AGENTS.md)
263+
264+
```go
265+
const ProjectFileName = "AGENTS.md"
266+
func LoadProjectFile() string
267+
```
268+
269+
`LoadProjectFile` reads `AGENTS.md` from the current working directory. When `NoProjectFile` is false (default), `New()` calls this automatically and appends the content to the system message with a `# Project Instructions` header. Use this for project-level conventions, architecture notes, and coding standards.
270+
271+
---
272+
273+
## Complete Example
274+
275+
```go
276+
package main
277+
278+
import (
279+
"context"
280+
"encoding/json"
281+
"fmt"
282+
"os"
283+
"strings"
284+
285+
"github.com/BackendStack21/kode"
286+
)
287+
288+
// Custom tool: count words in a string
289+
type wordCountTool struct{}
290+
291+
func (t *wordCountTool) Name() string { return "word_count" }
292+
293+
func (t *wordCountTool) Description() string {
294+
return "Count the number of words in a given text."
295+
}
296+
297+
func (t *wordCountTool) Schema() any {
298+
return map[string]any{
299+
"type": "object",
300+
"properties": map[string]any{
301+
"text": map[string]any{
302+
"type": "string",
303+
"description": "The text to count words in",
304+
},
305+
},
306+
"required": []string{"text"},
307+
}
308+
}
309+
310+
func (t *wordCountTool) Call(args string) (string, error) {
311+
var params struct {
312+
Text string `json:"text"`
313+
}
314+
if err := json.Unmarshal([]byte(args), &params); err != nil {
315+
return "", err
316+
}
317+
count := len(strings.Fields(params.Text))
318+
return fmt.Sprintf("Word count: %d", count), nil
319+
}
320+
321+
func main() {
322+
agent, err := odek.New(odek.Config{
323+
Model: "deepseek-v4-flash",
324+
APIKey: os.Getenv("DEEPSEEK_API_KEY"),
325+
SystemMessage: "You are an expert at analyzing text.",
326+
Tools: []odek.Tool{&wordCountTool{}},
327+
MaxIterations: 10,
328+
})
329+
if err != nil {
330+
fmt.Fprintf(os.Stderr, "odek: %v\n", err)
331+
os.Exit(1)
332+
}
333+
defer agent.Close()
334+
335+
result, err := agent.Run(context.Background(), "Count the words in the first paragraph of the README.")
336+
if err != nil {
337+
fmt.Fprintf(os.Stderr, "run: %v\n", err)
338+
os.Exit(1)
339+
}
340+
fmt.Println(result)
341+
}
342+
```
343+
344+
---
345+
346+
## Import Path
347+
348+
```
349+
module github.com/your-module
350+
351+
go 1.25.0
352+
353+
require github.com/BackendStack21/kode v0.15.0
354+
```
355+
356+
odek exposes a single package at `github.com/BackendStack21/kode`. All internal packages (`internal/llm`, `internal/memory`, `internal/skills`, etc.) are private and not importable outside the module. The public surface is:
357+
358+
| Symbol | Kind | Description |
359+
|--------|------|-------------|
360+
| `New` | func | Agent constructor |
361+
| `Config` | struct | Agent configuration |
362+
| `Agent` | struct | Agent runtime with Run, Close, Memory |
363+
| `Tool` | interface | Tool plugin interface |
364+
| `ModelProfile` | struct | Per-model defaults |
365+
| `KnownProfiles` | var | Built-in model profiles |
366+
| `LookupProfile` | func | Best-match profile lookup |
367+
| `ProfileLabel` | func | Human-readable model label |
368+
| `ProjectFileName` | const | AGENTS.md |
369+
| `LoadProjectFile` | func | Read project instructions file |

docs/DEVELOPMENT.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ cmd/odek/
8080
index.html Single-page web UI (~770 LOC, vanilla JS + CSS)
8181
docs/ Documentation
8282
CLI.md CLI reference
83+
API.md Programmatic API (Go library)
8384
CONFIG.md Configuration system
8485
PROVIDERS.md Models, profiles, thinking, context
8586
SESSIONS.md Multi-turn sessions

docs/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,7 @@ <h2>Everything else is <span class="hl">documented</span></h2>
648648
<div class="doc-links">
649649
<a href="https://raw.githubusercontent.com/BackendStack21/kode/main/docs/CLI.md">CLI Reference<span>All commands, subcommands, flags, examples</span></a>
650650
<a href="https://raw.githubusercontent.com/BackendStack21/kode/main/docs/CHEATSHEET.md">Cheat Sheet<span>Quick reference: commands, config, memory, subagents, env vars</span></a>
651+
<a href="https://raw.githubusercontent.com/BackendStack21/kode/main/docs/API.md">Programmatic API<span>Go library: Tool interface, Config, Agent, model profiles</span></a>
651652
<a href="https://raw.githubusercontent.com/BackendStack21/kode/main/docs/CONFIG.md">Configuration<span>Config files, env vars, priority chain</span></a>
652653
<a href="https://raw.githubusercontent.com/BackendStack21/kode/main/docs/PROVIDERS.md">Providers & Models<span>Deepseek, OpenAI, Anthropic, Ollama, vLLM, more</span></a>
653654
<a href="https://raw.githubusercontent.com/BackendStack21/kode/main/docs/MCP.md">MCP (Two-Way)<span>Serve tools + connect to external MCP servers</span></a>

0 commit comments

Comments
 (0)