Documentation
¶
Overview ¶
Package bonnie is the entry point of an authored agent tree.
A BONNIE agent is a directory of files whose meaning comes from their paths, and one Go file that starts it:
package main
import "github.com/mark3labs/bonnie"
func main() { bonnie.New().Serve() }
That is the whole default agent. Every slot in the tree has a framework default, and authoring the slot replaces it: instructions.md is the system prompt, workspace/ is the directory the agent's files live in, tools/ holds one directory per tool, and .bonnie holds the journal. Configuration that is not a file is code — an Option on New:
func main() {
bonnie.New(
bonnie.WithModel("anthropic/claude-sonnet-4-5"),
bonnie.WithSandbox(sandbox.Docker()),
).Serve()
}
There is no manifest file. Data-shaped settings live at their default paths, and everything else is a Go call, so a setting that does not exist is a compile error rather than a key that is accepted and ignored.
The tree's code — its tools — and the copies of its data files that a `bonnie build` binary carries are wired by codegen into bonnie_gen.go, which calls Register from its init. main.go never has to name them.
Index ¶
- Constants
- func Register(t Tree)
- type Agent
- type Channel
- type ChannelFunc
- type Option
- func Quiet() Option
- func WithAddr(addr string) Option
- func WithAgentFactory(f runtime.AgentFactory) Option
- func WithChannel(f ChannelFunc) Option
- func WithDiscord(cfg discord.Config) Option
- func WithGitHub(cfg github.Config) Option
- func WithHTTPAuthenticator(fn bonniehttp.Authenticator) Option
- func WithInstructions(path string) Option
- func WithJournal(dir string) Option
- func WithKit(opts ...kit.Option) Option
- func WithListener(ln net.Listener) Option
- func WithModel(model string) Option
- func WithName(name string) Option
- func WithNetwork(p sandbox.NetworkPolicy) Option
- func WithSandbox(p sandbox.Provider) Option
- func WithSandboxEnv(env map[string]string) Option
- func WithShutdownTimeout(d time.Duration) Option
- func WithSkills(dir string) Option
- func WithSlack(cfg slack.Config) Option
- func WithSystemPrompt(prompt string) Option
- func WithTelegram(cfg telegram.Config) Option
- func WithTools(tools ...kit.Tool) Option
- func WithWorkspace(dir string) Option
- type Tree
Constants ¶
const ( // DefaultInstructions is the system prompt file, relative to the tree. DefaultInstructions = "instructions.md" // DefaultWorkspace is the directory the agent's files live in: the // working directory of the host's file tools without a sandbox, and the // seed mirrored into the sandbox with one. DefaultWorkspace = "workspace" // DefaultSkills is the tree's skills directory. DefaultSkills = "skills" // DefaultJournal is the directory the run journal is written to. DefaultJournal = ".bonnie" // DefaultAddr is the address the HTTP channel binds when none is given. DefaultAddr = ":8080" )
The default layout of a scaffolded tree. These are the paths `bonnie init` writes, the paths codegen embeds, and the paths Agent.Run reads when no option overrides them. They are constants rather than five copies of a string literal, because the scaffold, the generator, the dev loop, and the runtime must agree on the answer: a rule written more than once is a rule one caller can honour while another misses it.
const DefaultDotenv = ".env"
DefaultDotenv is the environment file Agent.Run loads when it is present in the process's working directory.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Agent ¶
type Agent struct {
// contains filtered or unexported fields
}
Agent is a configured agent: the tree's defaults with the options applied over them. Build one with New, then Agent.Serve it.
Nothing is opened, bound, or read until it serves, so building an agent cannot fail and New returns no error. A setting that cannot apply — a network policy with no sandbox to enforce it, a model beside a host-supplied agent factory — is refused when serving starts, which is the first moment the whole configuration is known.
func New ¶
New builds an agent from the tree's default layout and the options.
func main() { bonnie.New().Serve() }
With no options that is a complete agent: instructions.md is the system prompt, workspace/ is the agent's root for files, .bonnie is the journal, the tools under tools/ are wired by codegen, and the HTTP channel is served on :8080. Each Option replaces one of those.
func (*Agent) Run ¶
Run serves the agent until ctx ends, then drains in-flight turns and returns. It is Agent.Serve without the process: no flags, no signal handler, no exit — for a host that already owns those.
A run that parks holds no compute and lives in the journal, so stopping here is never destructive.
func (*Agent) Serve ¶
func (a *Agent) Serve()
Serve runs the agent until the process is interrupted, then exits.
It owns the process, which is what makes a one-line main possible: it parses the operator flags a serving binary accepts, installs the signal handler, drains in-flight turns on SIGINT or SIGTERM, and exits non-zero after writing the error to stderr. A host that owns its own process calls Agent.Run instead.
The flags are -addr and -model, and each wins over the matching option, so an operator can move a built binary to another port or model without rebuilding it. `bonnie dev` starts a tree's binary with -addr, which is the whole contract between the dev loop and the child.
type Channel ¶
Channel is an inbound transport BONNIE can mount: the HTTP routes it needs and the inbound surface a run resolves through. Every adapter in channel/ is both.
type ChannelFunc ¶
ChannelFunc builds a channel for the runner that serves it. It is called once at start; an error stops the process before it listens.
type Option ¶
type Option func(*config)
Option configures New. This is where a setting that is not a file in the tree lives: the model, a sandbox, an extra channel. A setting that does not exist is a compile error, which is the point.
func Quiet ¶
func Quiet() Option
Quiet suppresses the startup banner.
It no longer has to make an exception for a no-sandbox warning, because no run is unsandboxed: the banner names the backend in force instead, and a host that silences it has still chosen a confined run.
func WithAddr ¶
WithAddr binds the HTTP channel to addr instead of DefaultAddr.
func WithAgentFactory ¶
func WithAgentFactory(f runtime.AgentFactory) Option
WithAgentFactory replaces the model-backed agent entirely with one the host builds itself. It is the escape hatch for a program that implements runtime.Agent — a test double, a router, a second framework — and wants BONNIE only for durability and transport.
It cannot be combined with the options that configure the agent BONNIE would have built (WithModel, WithSystemPrompt, WithSandbox, WithSandboxEnv, WithNetwork, WithTools, WithKit): the factory owns the agent, so those settings would be accepted and ignored. Agent.Run refuses instead, naming both.
What the factory owns, it owns completely: the tree's instructions and the tools codegen discovered do not reach it either. They are available through Registered for a host that wants them. The journal, the workspace, the channels, and the shutdown behaviour are unaffected — those are BONNIE's side of the boundary.
func WithChannel ¶
func WithChannel(f ChannelFunc) Option
WithChannel mounts another inbound transport beside the HTTP channel.
func WithDiscord ¶
WithDiscord mounts the Discord channel. DISCORD_BOT_TOKEN and DISCORD_PUBLIC_KEY come from the environment; see WithSlack.
func WithGitHub ¶ added in v0.5.0
WithGitHub mounts the GitHub App channel. GITHUB_APP_ID, GITHUB_APP_PRIVATE_KEY, and GITHUB_WEBHOOK_SECRET come from the environment; see WithSlack. GITHUB_INSTALLATION_ID comes from there too, and only a hand-off needs it: a webhook carries its own installation. The bot name is configured, not environmental: it is a setting, not a secret.
func WithHTTPAuthenticator ¶ added in v0.7.0
func WithHTTPAuthenticator(fn bonniehttp.Authenticator) Option
WithHTTPAuthenticator verifies who is calling the framework's own HTTP API, and makes the principal it returns the run's identity.
Every chat adapter already does this from a platform signature — Slack's HMAC, Discord's Ed25519, GitHub's HMAC. The HTTP channel carries no such signature, so what counts as proof is the host's to decide: a bearer token, an OIDC assertion, a client certificate, a session cookie.
Without this option the HTTP API authenticates nobody. That is the right default for a loopback `bonnie dev`, and the wrong one for anything reachable by someone else — the routes start runs, read transcripts, and retire conversations. A deployment either passes an authenticator here or puts the process behind something that has already established who is calling.
See github.com/mark3labs/bonnie/channel/http.Authenticator for what a verifier returns, and github.com/mark3labs/bonnie/channel/http.ErrUnauthenticated for how it refuses one.
func WithInstructions ¶
WithInstructions reads the system prompt from path instead of DefaultInstructions. An empty path means the agent has no instructions file, which is how a host with no tree runs.
func WithJournal ¶
WithJournal writes the run journal to dir instead of DefaultJournal.
func WithKit ¶
WithKit passes Kit options through to the agent, for settings BONNIE does not name itself.
func WithListener ¶
WithListener serves on an already-bound listener instead of dialling the configured address. Tests bind :0 with it and learn the port.
func WithModel ¶
WithModel selects the model, as "provider/name". Without it, Kit's default applies.
func WithName ¶ added in v0.5.0
WithName names the agent. The name is reported by `GET /bonnie/v1/info` and nowhere else today; it is for a client that talks to several agents.
func WithNetwork ¶
func WithNetwork(p sandbox.NetworkPolicy) Option
WithNetwork constrains what the sandbox may reach. It needs a backend that can enforce it: a policy is refused at startup by a backend that cannot, never stored and ignored. The default Landlock backend confines the filesystem and not the network, so a policy with it is refused and names the backends that can.
func WithSandbox ¶
WithSandbox selects the backend every tool call runs in.
It SELECTS a sandbox, it does not enable one. Leaving it out does not give the model this process's filesystem: a run with no explicit backend gets sandbox.Landlock, which confines tool calls to the run's own workspace using the Linux Landlock LSM and needs nothing installed.
Pass this to choose something stronger — sandbox.Docker for namespaces, sandbox.Microsandbox for a microVM with its own kernel — or to widen the confinement deliberately with sandbox.Local, which provides no isolation at all and is for development only.
The godoc on each provider in package sandbox states what that backend does and does not contain.
func WithSandboxEnv ¶ added in v0.7.0
WithSandboxEnv injects environment variables into every command the sandbox runs. It is how a run gets a credential or a setting the model must not choose — a database URL, a registry token, a feature flag — configured out of band so the model never holds the value.
The variables reach every backend, because the injection travels over the same seam a per-command environment does. An injected value wins over a per-command variable of the same name, so a fixed secret cannot be clobbered. Repeated calls merge, and a later key overrides an earlier one. See sandbox.EnvInjected for the full statement.
It does not encrypt the value or hide it from a command the model runs: a command inside the sandbox can print any variable it is given. The isolation is that the model cannot choose what is injected, not that it cannot read it.
func WithShutdownTimeout ¶
WithShutdownTimeout is how long Agent.Run waits for in-flight turns to reach a checkpoint after a signal. A turn that is cut short still keeps its finished steps — the journal is what survives — but a clean stop is cheaper.
func WithSkills ¶ added in v0.7.0
WithSkills reads the tree's skills from dir instead of DefaultSkills. An empty dir means the agent loads no skills at all, which is how a host with no tree runs.
A skill is a markdown file with YAML frontmatter: dir holds one *.md or *.txt per skill, or one subdirectory per skill with a SKILL.md in it. Kit scans the directory as given and adds nothing to the path, so BONNIE's skills/ is the whole skill set — the agent never inherits a skill from a .agents/skills it happens to be running beside.
Each skill's name and description reach the system prompt; the body arrives only when the model calls activate_skill. A bundled scripts/, references/, or assets/ file is NAMED in that activation text but lives on the host, outside the sandbox the tools run in, so the model cannot open it. Put what the model must read in the skill body, and put a file it must open in workspace/.
func WithSlack ¶
WithSlack mounts the Slack channel. Credentials come from the environment and never from code: SLACK_BOT_TOKEN and SLACK_SIGNING_SECRET fill the config's empty fields, and a missing one is a startup error naming the variable. A webhook that does not verify its caller is a door with no lock.
func WithSystemPrompt ¶
WithSystemPrompt sets the system prompt directly, instead of reading the tree's instructions file. It wins over WithInstructions.
func WithTelegram ¶
WithTelegram mounts the Telegram channel. TELEGRAM_BOT_TOKEN and TELEGRAM_WEBHOOK_SECRET come from the environment; see WithSlack.
func WithTools ¶
WithTools adds tools to the set the model may call, beside the tools codegen discovered under tools/ and Kit's core set.
func WithWorkspace ¶
WithWorkspace roots the agent's files at dir instead of DefaultWorkspace. An empty dir means no workspace: the process's own directory stays the root, which is how a host with no tree runs.
The workspace is what keeps a model's write off the tree itself — the instructions, the journal, and the source beside them.
type Tree ¶
type Tree struct {
// Tools are the tools discovered under tools/, one per directory.
Tools []kit.Tool
// Instructions is the embedded copy of the tree's instructions file.
Instructions string
// Skills is the embedded copy of the tree's skills directory.
// [Agent.Run] materialises it beside the journal when the tree's skills
// directory is not on disk, which is what a built binary on a bare host
// has, and hands the directory to Kit as Options.SkillsDir.
Skills embed.FS
// Workspace is the embedded copy of the tree's workspace seed files.
// [Agent.Run] materialises them beside a built binary that has no tree, and
// never overwrites a file that is already there.
Workspace embed.FS
}
Tree is what codegen discovered in an agent tree: the code it wired and the data files it embedded. The generated bonnie_gen.go builds one and hands it to Register from its init, so main.go never names a tool or an embed.
A tree run from its source directory reads its data files from disk and uses the embedded copies only as a fallback. A binary from `bonnie build` has no tree beside it, so the embedded copies are all it has.
func Registered ¶
func Registered() Tree
Registered returns what the generated file registered. It is the zero Tree when a tree has not been generated yet, which is what a hand-written main.go with no tools sees.
Call it at run time — inside main, or later. The generated file registers from init, and Go initialises every package-level variable before it runs any init function, so a package-level `var x = bonnie.Registered()` reads the empty tree.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package agent implements BONNIE's L2 discovery: the authored agent tree.
|
Package agent implements BONNIE's L2 discovery: the authored agent tree. |
|
Package channel defines BONNIE's inbound transport abstraction (L3).
|
Package channel defines BONNIE's inbound transport abstraction (L3). |
|
chat
Package chat holds the plumbing every chat-platform channel shares: the journalled address map, per-run turn locks, the channel.SessionRef implementation, and the dispatch rule that sends a message to the right runner entry point.
|
Package chat holds the plumbing every chat-platform channel shares: the journalled address map, per-run turn locks, the channel.SessionRef implementation, and the dispatch rule that sends a message to the right runner entry point. |
|
discord
Package discord is BONNIE's Discord inbound transport (L3).
|
Package discord is BONNIE's Discord inbound transport (L3). |
|
github
Package github is BONNIE's GitHub App channel (L3).
|
Package github is BONNIE's GitHub App channel (L3). |
|
http
Package http is BONNIE's HTTP inbound transport (L3).
|
Package http is BONNIE's HTTP inbound transport (L3). |
|
slack
Package slack is BONNIE's Slack inbound transport (L3).
|
Package slack is BONNIE's Slack inbound transport (L3). |
|
telegram
Package telegram is BONNIE's Telegram inbound transport (L3).
|
Package telegram is BONNIE's Telegram inbound transport (L3). |
|
Package channeltest is the conformance suite for channel adapters, in the same spirit as the journal and sandbox suites.
|
Package channeltest is the conformance suite for channel adapters, in the same spirit as the journal and sandbox suites. |
|
Package client is a Go client for BONNIE's HTTP wire API.
|
Package client is a Go client for BONNIE's HTTP wire API. |
|
cmd
|
|
|
bonnie
command
Command bonnie is the BONNIE developer CLI.
|
Command bonnie is the BONNIE developer CLI. |
|
bonnie/tui
Package tui is the BONNIE terminal user interface.
|
Package tui is the BONNIE terminal user interface. |
|
examples
|
|
|
github-bot
command
Command github-bot is a reference for BONNIE's GitHub channel: the one bonnie.New call that turns a program into an agent which speaks on GitHub.
|
Command github-bot is a reference for BONNIE's GitHub channel: the one bonnie.New call that turns a program into an agent which speaks on GitHub. |
|
slack-bot
command
Command slack-bot is a reference for BONNIE's Slack channel: the one bonnie.New call that turns a program into an agent which speaks in Slack.
|
Command slack-bot is a reference for BONNIE's Slack channel: the one bonnie.New call that turns a program into an agent which speaks in Slack. |
|
internal
|
|
|
treetest
Package treetest builds throwaway agent trees that compile against this checkout, for tests that need a real `go build` of a scaffolded module.
|
Package treetest builds throwaway agent trees that compile against this checkout, for tests that need a real `go build` of a scaffolded module. |
|
Package runtime is BONNIE's durable execution layer (L1).
|
Package runtime is BONNIE's durable execution layer (L1). |
|
Package sandbox gives a BONNIE run an isolated place to run tool calls.
|
Package sandbox gives a BONNIE run an isolated place to run tool calls. |