Documentation
¶
Overview ¶
Package channel is the front-door abstraction: every place a user can talk to tomo (the web UI, Telegram, and later Discord, Slack, iMessage) is a Channel. Adapters stay thin. They translate their platform's messages into an Inbound, render replies through a Reply, and answer approvals through an Approver. Everything above that (sessions, the agent turn, policy) is the router's job and is written once.
Index ¶
- Constants
- func DecodeDataURL(s string) (provider.Block, error)
- func Drivers() []string
- func FetchImage(ctx context.Context, client *http.Client, url string, header http.Header) (provider.Block, error)
- func ReadImageFile(path, declaredType string) (provider.Block, error)
- func Register(name string, d Driver)
- type AgentFunc
- type Attachment
- type Caps
- type Channel
- type Clip
- type Driver
- type Exchange
- type FileReply
- type Handler
- type Inbound
- type Reply
- type Router
- type Settings
- type VoiceReply
- type Workforce
Constants ¶
const DefaultWorker = "tomo"
DefaultWorker is the name of the always-present worker: tomo itself. A deployment with no specialists configured routes everything here.
Variables ¶
This section is empty.
Functions ¶
func DecodeDataURL ¶
DecodeDataURL turns a browser data: URL into an image block. The webchat UI posts pasted or attached images this way, so it never touches the network.
func Drivers ¶ added in v0.2.1
func Drivers() []string
Drivers lists the registered channel names in sorted order, for onboarding and for a scaffold that wants to check a name is free.
func FetchImage ¶
func FetchImage(ctx context.Context, client *http.Client, url string, header http.Header) (provider.Block, error)
FetchImage downloads one image and returns it as a provider image block. It is the shared path every channel uses to turn an attachment into something the model can see. header carries any auth a platform's file URL needs. A non-image response, or one too large, is an error the caller can skip.
func ReadImageFile ¶
ReadImageFile reads an image off disk and returns it as a model-ready block. iMessage stores attachments as files, so this is that channel's path to vision. declaredType is the mime type the database recorded, if any; the file extension is the fallback.
Types ¶
type AgentFunc ¶
AgentFunc returns a ready base agent, minus its gate. It runs once per message so the system prompt reflects the current memory index.
type Attachment ¶
type Attachment struct {
Name string // suggested filename, like "chart.png"
Mime string // media type, like "image/png"
Data []byte // the file bytes
Caption string // optional note to show alongside it
}
Attachment is a file the agent made for the user: an image, a chart, a screenshot, a document. It rides the send_file tool back out.
type Caps ¶
type Caps struct {
Media bool // can carry images in and out
Buttons bool // can render tap-to-approve controls
Stream bool // can update a message as text streams
}
Caps declares what a channel can do, so the router can degrade gracefully: no Buttons means approvals fall back to a yes/no text reply, no Stream means the reply is sent once at the end instead of incrementally.
type Clip ¶
type Clip struct {
Data []byte // the raw audio
Ext string // container extension, like ".ogg" or ".m4a"
}
Clip is a speech attachment waiting to be transcribed. The router turns it into text before the turn runs; channels only have to carry the bytes and the container extension.
func DecodeAudioDataURL ¶
DecodeAudioDataURL turns a browser data: URL into a voice clip, the shape the webchat UI posts a recording as.
func FetchAudio ¶
func FetchAudio(ctx context.Context, client *http.Client, url string, header http.Header) (Clip, error)
FetchAudio downloads one voice note and returns it as a clip. header carries any auth the platform's file URL needs. A response that is too large is an error the caller can skip; anything else is taken as audio, since the transcriber decodes by content, not by a strict type check.
func ReadAudioFile ¶
ReadAudioFile reads a voice note off disk, for the iMessage channel whose attachments are files.
type Driver ¶ added in v0.2.1
Driver opens a channel from its settings. A driver package registers itself under a name in init(), so the core reaches a channel by name and never imports the channel's package. This mirrors how a database driver owns its own connection string: the standard library dispatches by name and stays ignorant of any one driver's dialect.
Open returns (nil, nil) to mean "configured but off", which lets a driver treat an empty or disabled block as a no-op rather than an error.
type Exchange ¶
Exchange bundles one inbound message with the channel-side handles the router needs to answer it.
type FileReply ¶
type FileReply interface {
File(a Attachment)
}
FileReply is an optional capability a Reply may implement: it can deliver a file the agent produced. A channel that cannot carry files leaves it unimplemented, and the send_file tool tells the agent where the file is instead.
type Inbound ¶
type Inbound struct {
Chat string // conversation key within the channel
User string // sender id, for allowlisting
Text string // message text
Images []provider.Block // any image blocks that came with it
Audio []Clip // any voice notes that came with it, pre-transcription
}
Inbound is one message arriving from a channel.
type Reply ¶
Reply is how the router talks back during one turn. Chunk carries streamed assistant text, Notice carries out-of-band status (tool activity, errors), and Done finalizes the turn.
type Router ¶
type Router struct {
// contains filtered or unexported fields
}
Router turns an inbound message into a persisted, policy-gated agent turn and streams the reply back. It is channel-agnostic: HandlerFor binds it to a named channel and returns the Handler that channel drives. A Workforce decides which worker handles each message and builds that worker's agent.
func NewRouter ¶
func NewRouter(st *store.Store, work Workforce, auditor policy.Auditor, transcriber voice.Transcriber, synth voice.Synthesizer) *Router
NewRouter wires a router. transcriber and synth may each be nil: without a transcriber a voice note is acknowledged but not understood, and without a synth a reply is never spoken back.
func (*Router) Background ¶
Background runs a one-off prompt as a turn in the chat's session and returns the assistant's text, for the scheduler to deliver. No one is watching, so any tool call that would ask for approval is declined; the rest of the policy stays in force.
func (*Router) HandlerFor ¶
HandlerFor returns the Handler for a channel by name.
type Settings ¶ added in v0.2.1
Settings is one channel's configuration, decoded from the YAML block under channels.<name>. The core is deliberately blind to what any single key means: a Telegram token, a Discord allow-list, an iMessage database path are all just entries here, and only the channel's own driver knows how to read them. That is the whole point of the split. A new channel is a new package that registers a driver; the core never grows a field for it.
The accessors coerce the loose types a YAML decode produces (a number may arrive as int, int64, or float64; a list as []any) into the shapes a driver asks for, so a driver states what it wants and does not repeat the coercion.
func (Settings) Bool ¶ added in v0.2.1
Bool returns the value at key as a bool, defaulting to false.
func (Settings) Int64s ¶ added in v0.2.1
Int64s returns the value at key as a list of int64. It accepts the several numeric types a YAML decode can hand back, and a lone number as a one-element list.
type VoiceReply ¶
type VoiceReply interface {
Voice(clip Clip)
}
VoiceReply is an optional capability a Reply may also implement: it can send a synthesized audio clip back. The router calls it when it has spoken a reply and the channel can carry audio out. A channel that cannot just leaves it unimplemented and the reply stays text only.
type Workforce ¶
type Workforce interface {
// Route picks the worker for a message. An explicit @name prefix wins, then
// a channel:chat binding, otherwise the default worker. It returns the
// worker name and the text with any @name prefix stripped.
Route(channel, chat, text string) (worker, cleaned string)
// Agent builds a fresh agent for one worker, so its system prompt reflects
// the worker's current memory.
Agent(worker string) (*agent.Agent, error)
// Engine returns the policy engine for one worker.
Engine(worker string) *policy.Engine
// Curator returns the reflection pass for one worker, or nil if none runs.
Curator(worker string) *curator.Curator
// Names lists every known worker, the default included.
Names() []string
}
Workforce resolves an inbound message to the worker that should handle it and builds that worker's agent, policy, and curator. A worker is a named specialist with its own persona, memory, and gate; the default worker is tomo. The router owns none of this itself, so how workers are configured stays out of pkg/channel.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package discord is tomo's Discord channel.
|
Package discord is tomo's Discord channel. |
|
Package imessage is a no-op everywhere but macOS.
|
Package imessage is a no-op everywhere but macOS. |
|
Package slack is tomo's Slack channel.
|
Package slack is tomo's Slack channel. |
|
Package telegram is tomo's Telegram channel.
|
Package telegram is tomo's Telegram channel. |
|
Package webchat is tomo's built-in channel: a small single-page UI served on localhost and a streaming endpoint behind it.
|
Package webchat is tomo's built-in channel: a small single-page UI served on localhost and a streaming endpoint behind it. |