Documentation
¶
Overview ¶
Package tomte builds small agents that do one job. An agent is a Go program: it declares its instructions, model, tools and channel, and compiles to one binary. See docs/design.md.
Index ¶
- Variables
- func StateDir() string
- type Agent
- type Channel
- type File
- type Incoming
- type Message
- type Option
- func Command(name, description string, fn func(context.Context, Request) (string, error)) Option
- func Cron(spec, task string) Option
- func HiddenCommand(name, description string, fn func(context.Context, Request) (string, error)) Option
- func Instructions(s string) Option
- func Job(spec, name string, fn func(context.Context) (string, error)) Option
- func Log(path string) Option
- func MaxCost(perRun, perDay float64) Option
- func MaxSteps(n int) Option
- func Model(baseURL, name, key string) Option
- func Owner(from string) Option
- func Price(in, out float64) Option
- func ReadsJournal() Option
- func SendTo(cs ...Channel) Option
- func Timeout(d time.Duration) Option
- func Tool[In, Out any](name, description string, fn func(context.Context, In) (Out, error)) Option
- type Receiver
- type Request
- type Result
- type Usage
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrCostLimit = errors.New("cost limit reached") ErrDailyLimit = errors.New("daily cost limit reached") )
var ErrStepLimit = errors.New("step limit reached")
ErrStepLimit is returned when a run uses all its steps without replying.
var ErrTimeLimit = errors.New("time limit reached")
ErrTimeLimit is returned when a run, job or command takes longer than the agent's Timeout.
Functions ¶
Types ¶
type Agent ¶
type Agent struct {
// contains filtered or unexported fields
}
Agent is one agent: its declaration, from New, and its run log. Run is its main; Do runs it on one task from Go code. An Agent is safe to use from several goroutines.
func New ¶
New declares an agent. Mistakes in the options are reported when it runs, so the declaration reads as one expression.
Example ¶
An agent that chases unpaid invoices every weekday morning and tells a Telegram chat what it did.
package main
import (
"context"
"os"
"github.com/berkaycubuk/tomte"
"github.com/berkaycubuk/tomte/channels/telegram"
)
type overdueArgs struct {
Days int `json:"days" jsonschema:"minimum age in days"`
}
type invoice struct {
Number string `json:"number"`
Customer string `json:"customer"`
Amount float64 `json:"amount"`
}
func findOverdue(ctx context.Context, in overdueArgs) ([]invoice, error) {
return []invoice{{"2026-041", "Acme", 1200}}, nil
}
func main() {
tomte.New("invoice-chaser",
tomte.Instructions("You chase unpaid invoices. List the overdue ones and say who to remind."),
tomte.Model("https://openrouter.ai/api/v1", "z-ai/glm-4.6", os.Getenv("MODEL_KEY")),
tomte.Tool("find_overdue", "List unpaid invoices older than N days", findOverdue),
tomte.Cron("0 9 * * 1-5", "Chase invoices overdue by 14 days or more."),
tomte.SendTo(telegram.New(os.Getenv("TELEGRAM_TOKEN"), os.Getenv("TELEGRAM_CHAT"))),
tomte.Price(0.60, 2.20),
tomte.MaxCost(0.50, 5.00),
).Run()
}
Output:
type Channel ¶
Channel is where an agent's results go, given with SendTo. Name says what it is in logs; Send delivers one message, and should give up when ctx is done.
type File ¶ added in v0.1.1
File is a document sent with a message, like a report as a PDF. Name is what the recipient sees it as, "report.pdf".
type Incoming ¶
Incoming is a message someone wrote to the agent. From is the sender in the channel's own terms; a Message To them answers it.
type Message ¶
Message is what an agent sends: markdown text, from the named agent. To is who gets it, in the channel's own terms (a Telegram chat id); a channel sends a message without one to its usual recipient. Files go with the text, after it; a channel that can't carry files says in the text which ones it left out.
type Option ¶
type Option func(*Agent)
Option is part of an agent's declaration, given to New: Model, Tool, Cron and the rest.
func Command ¶
Command answers "/name …" sent to the agent through its channel, while it's serving. What fn returns goes back to the sender unless it's empty or NOTHING. An error is logged, and the sender only hears it didn't work: errors are written for whoever runs the agent, not for strangers.
Example ¶
A command answers "/echo hello" written to the bot, with Go code.
package main
import (
"context"
"os"
"strings"
"github.com/berkaycubuk/tomte"
"github.com/berkaycubuk/tomte/channels/telegram"
)
func main() {
echo := func(ctx context.Context, r tomte.Request) (string, error) {
return strings.ToUpper(r.Args), nil
}
tomte.New("echo",
tomte.Command("echo", "Say it back, louder", echo),
tomte.SendTo(telegram.New(os.Getenv("TELEGRAM_TOKEN"), os.Getenv("TELEGRAM_CHAT"))),
).Run()
}
Output:
func HiddenCommand ¶ added in v0.1.2
func HiddenCommand(name, description string, fn func(context.Context, Request) (string, error)) Option
HiddenCommand is a Command left out of the list of commands, for one only the agent's owner uses, like /approve on a public bot. The Owner still sees it listed. Anyone can send it, so fn checks who's asking.
func Instructions ¶
Instructions is the agent's system prompt: who it is and what its job is.
func Job ¶
Job runs fn on a schedule while the agent is serving, and from the command line with `run <name>`. What fn returns is sent through the channel unless it's empty or NOTHING; an error is sent as a failure.
Example ¶
A job is Go code on a schedule, with no model. It replies NOTHING when there's nothing to say, and nothing is sent.
package main
import (
"context"
"fmt"
"os"
"github.com/berkaycubuk/tomte"
"github.com/berkaycubuk/tomte/channels/telegram"
)
func main() {
diskCheck := func(ctx context.Context) (string, error) {
free := 42 // percent, from syscall.Statfs in a real agent
if free > 10 {
return "NOTHING", nil
}
return fmt.Sprintf("Disk is %d%% free.", free), nil
}
tomte.New("disk-watch",
tomte.Job("@hourly", "disk", diskCheck),
tomte.SendTo(telegram.New(os.Getenv("TELEGRAM_TOKEN"), os.Getenv("TELEGRAM_CHAT"))),
).Run()
}
Output:
func Log ¶
Log sets the file the run log is appended to. It's <name>.jsonl in $STATE_DIRECTORY (set by systemd's StateDirectory=) or else in the working directory.
func MaxCost ¶
MaxCost limits what the agent spends, in dollars, per run and per day (local time). Zero means no limit. It needs Price.
func Model ¶
Model sets the OpenAI-compatible API the agent thinks with, for example Model("https://openrouter.ai/api/v1", "z-ai/glm-4.6", os.Getenv("MODEL_KEY")).
func Owner ¶ added in v0.1.3
Owner is the sender, in the channel's terms, who sees hidden commands in the list of commands, so they don't have to remember them.
func Price ¶
Price is what the model costs, in dollars per million input and output tokens, as the provider lists it. Runs are priced only when it's given.
func ReadsJournal ¶
func ReadsJournal() Option
ReadsJournal lets the agent read the system journal, sshd's and sudo's lines included, when it's deployed with the tomte command: its user joins the systemd-journal group. Nothing else changes.
func SendTo ¶
SendTo sets where the agent's results go; it's Stdout if not given. With several channels each result goes to all of them.
func Timeout ¶ added in v0.1.1
Timeout limits how long one run, job or command may take; it's 10 minutes if not given, and 0 means no limit.
func Tool ¶
Tool lets the model call fn. In must be a struct; its fields are the tool's arguments (see schema.go for the tags).
Example ¶
A tool's input is a struct: its fields are the arguments the model fills in, required unless omitempty.
package main
import (
"context"
"fmt"
"github.com/berkaycubuk/tomte"
)
func main() {
type args struct {
City string `json:"city" jsonschema:"the city to look up"`
Units string `json:"units,omitempty" jsonschema:"metric or imperial; metric if not given"`
}
weather := func(ctx context.Context, in args) (string, error) {
return fmt.Sprintf("12°C and raining in %s", in.City), nil
}
_ = tomte.Tool("weather", "The weather in a city now", weather)
}
Output:
type Receiver ¶
Receiver is a channel people can write to. Receive calls handle with each message until ctx is done, and returns then, or earlier when receiving can't work at all. handle may wait while the agent is busy.
type Request ¶
type Request struct {
From string // the sender, in the channel's terms: a Telegram chat id
Args string // what followed the command's name, trimmed
}
Request is a command someone sent.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
channels
|
|
|
telegram
Package telegram sends an agent's results to a Telegram chat through a bot.
|
Package telegram sends an agent's results to a Telegram chat through a bot. |
|
telekinesis
Package telekinesis sends an agent's results to a person through telekinesis (https://telekinesis.berkay.tools), which shows them in its web app with a push notification.
|
Package telekinesis sends an agent's results to a person through telekinesis (https://telekinesis.berkay.tools), which shows them in its web app with a push notification. |
|
cmd
|
|
|
tomte
command
Command tomte deploys and watches Tomte agents on a machine over ssh.
|
Command tomte deploys and watches Tomte agents on a machine over ssh. |
|
examples
|
|
|
dice
command
Dice is the smallest useful tomte: it rolls dice when asked.
|
Dice is the smallest useful tomte: it rolls dice when asked. |
|
hn-digest
command
HN digest sends a short morning digest of the Hacker News stories that match your interests to telekinesis.
|
HN digest sends a short morning digest of the Hacker News stories that match your interests to telekinesis. |
|
ssh-watch
command
SSH watch tells you on Telegram when someone logs in to this machine over ssh as a user, from an address or with a key it hasn't seen before.
|
SSH watch tells you on Telegram when someone logs in to this machine over ssh as a user, from an address or with a key it hasn't seen before. |