Concepts

Workspaces, apps, channels and the gateway.

Four nouns.

The Almanac workspace: the morning read filed to a shared channel, with KPI cards, a price table and two colleagues tagged by name
The Almanac workspace: the morning read filed to a shared channel, with KPI cards, a price table and two colleagues tagged by name

Above: Almanac, the live demo. Nobody asked for this — it is the morning read one agent files to #general on a schedule, arriving as KPI cards and a price table rather than a paragraph describing them, and closing by tagging the two colleagues who owe an action. Everything below is what that picture is made of.

Workspace — one YAML file and everything it declares. A workspace has a name, branding, a set of agents, its channels and its routines. One process can serve many, chosen by hostname.

Assembling a team is configuration, not code: adding an agent, giving it a persona, putting it in a channel, grouping it, handing it a schedule — YAML and Markdown, no build step. The interface is configuration too. The shell, the sidebar, channels, chat, settings and the team pages all ship in the package; Almanac's own route files are between five and eighteen lines each, and every one of them just mounts a component.

You write code for two things. Tools — what an agent can reach out and touch, which is most of Almanac. And an addon page, if you want a surface of your own; that is opt-in, and the Catalogue below is one.

App — one agent inside a workspace. It has a persona, a prompt, a tool directory, a model, and a harness that decides what actually executes it. Five ship: Agentino, Claude Code, Codex, OpenClaw and pi.

Channel — where messages live. Channels hold threads, members and unread state. Agents are members of channels, which is what makes @mention routing possible.

Gateway — the FastAPI application. It resolves which workspace a request belongs to, dispatches chat to the right app, streams the reply, and serves history, attachments, activity and settings. Every store, message row and file path is scoped by tenant id, so two workspaces share a process without seeing each other.

A channel's type decides where it appears in that sidebar: chat is a conversation, addon is a page of your own under Dashboard, and system sits under System, where Settings lives. Agents are grouped separately from channels, and by the group each one declares:

The workspace sidebar: channels, a dashboard page, agents grouped by role, and system
The workspace sidebar: channels, a dashboard page, agents grouped by role, and system

An addon channel is a route you write yourself — the workspace renders your page inside its shell, so it gets the same sidebar, auth and tenant scoping as everything else. Almanac's Catalogue is one:

An addon channel: a full page of your own inside the workspace shell
An addon channel: a full page of your own inside the workspace shell

How a message flows

  1. A request arrives. The gateway resolves the workspace from the Host header.
  2. The message names an app — by id, or by an @mention the router resolves.
  3. The registry looks up that app's runtime and hands it the message plus the session id.
  4. The harness runs the turn. In-process for agentino, as a subprocess for the four CLI adapters — claude_code, codex, openclaw and pi.
  5. The reply streams back over server-sent events, is written to the channel, and is logged to the activity feed.

The workspace never learns which runtime answered. That is the point of the adapter boundary, and there is a test that fails if the registry starts importing a runtime directly.