channel

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 6, 2026 License: MIT Imports: 21 Imported by: 0

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

View Source
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

func DecodeDataURL(s string) (provider.Block, error)

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 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

func ReadImageFile(path, declaredType string) (provider.Block, error)

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

type AgentFunc func() (*agent.Agent, error)

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 Channel

type Channel interface {
	Name() string
	Caps() Caps
	Run(ctx context.Context, h Handler) error
}

Channel receives messages until its context is cancelled.

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

func DecodeAudioDataURL(s string) (Clip, error)

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

func ReadAudioFile(path string) (Clip, error)

ReadAudioFile reads a voice note off disk, for the iMessage channel whose attachments are files.

type Exchange

type Exchange struct {
	Channel  string
	In       Inbound
	Reply    Reply
	Approver policy.Approver
}

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 Handler

type Handler func(ctx context.Context, x Exchange)

Handler processes one exchange to completion.

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.

func (Inbound) Message

func (in Inbound) Message() provider.Message

Message builds the provider message for an inbound, text first then images.

type Reply

type Reply interface {
	Chunk(text string)
	Notice(text string)
	Done()
}

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

func (r *Router) Background(ctx context.Context, ch, chat, prompt string) (string, error)

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

func (r *Router) HandlerFor(name string) Handler

HandlerFor returns the Handler for a channel by name.

func (*Router) WaitIdle

func (r *Router) WaitIdle()

WaitIdle blocks until every in-flight curation has finished. Used on shutdown so a reflection is not cut off mid-write, and by tests.

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.

func Solo

func Solo(newAgent AgentFunc, engine *policy.Engine, cur *curator.Curator) Workforce

Solo is the one-worker workforce: everything routes to the default worker. It wraps the pieces the router would otherwise hold directly, so a deployment with no specialists behaves exactly as a single agent. cur may be nil.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL