podium

module
v0.9.1 Latest Latest
Warning

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

Go to latest
Published: Nov 4, 2026 License: Apache-2.0

README

Podium

Your own AI orchestration system. One deployment, every team, on machines you own.

Apache-2.0 licence ci / go ci / web

Podium is how an organization puts AI to work on the jobs that actually move the business, and then watches it finish them.

A bug lands in Slack or Linear, and Podium reads the code, runs what it needs to run, and posts the cause back on the thread. A pull request needs a review, and Podium reads the diff and writes the review. A change needs writing, and Podium edits the repository, runs the tests, opens the pull request, and can check the result in a browser before it says it is done. Someone asks what the numbers did last week, and Podium queries the warehouse and answers in the same conversation. Those are four jobs. The next one is a playbook you write, for whatever part of the organization needs it.

One deployment covers all of them. Chat, Slack, Linear, and GitHub are doors into the same system. Each team gets its own tools, repositories, model, and credentials. When the work needs a machine, it runs in a sandbox on a worker you enrolled, and the answer comes back where the question started, with the logs and the files to show for it.

You run the whole thing. The control plane, the conductor, the secrets, and the workers are yours. The model keys are yours.


What it drives

A playbook is one job: an image, the tools that job may use, the repositories it may touch, the model it runs on, and the secrets it is allowed to hold. A turn that needs a machine is one run of one playbook, a task, and it ends when that run ends. A conversation that only needs an answer stays with the assistant, on the conductor, and does not start a container. That split is what you get with the host runtime on. Without it, every turn is a task. The Deploy section says which one a given install is.

The jobs are yours to define. Four that the shape is built for:

  • Investigate a bug. A Slack thread or a Linear ticket hands a playbook the repository and a shell. It reads the code, runs what it needs to run, and posts the findings back on the thread that asked.
  • Review code. A GitHub App you create in your own org turns a pull request you tag into a turn, and posts the review as that App. The same review can be asked for from Slack. It is one conversation either way.
  • Write code. A playbook with write access to a repository, a toolchain, and, when the change has a UI, a browser. It edits, runs the tests, opens the pull request, and can check the result before it says it is done. The playbook that develops Podium itself is that job, pointed at this repository: profile/README.md.
  • Answer an analytics question. A playbook whose image has the warehouse client, and whose secret list is that warehouse's credential. The question is answered in the thread. That credential is not mounted into any other job.

A playbook's secrets: list is what that job receives. The assistant picks the playbook for the piece of work in front of it, and may start more than one in a single answer. A thread can also name one (/playbook), and a Linear assignment runs the playbook that claims Linear. docs/agent.md is the full account of assistant, playbook, and task.

Where it runs

Each task is one container, on one worker, from one image, to one exit code.

  • Its own private network, shared only with the sidecars that task started.
  • A fresh workspace, mounted at /workspace, deleted when the task ends. What you meant to keep is an artifact in the object store.
  • Every capability dropped, no-new-privileges set, and the Docker socket never mounted in. A task does not get a daemon unless the playbook asks for one, and then the daemon is a sidecar the operator allowed on that node.
  • Secrets decrypted by the control plane, delivered onto a tmpfs at /podium/secrets, and shredded with the container. They are encrypted at rest under a master key you hold.

Workers dial out. A node opens one stream to the control plane and listens for nothing, so a worker on another continent is the same operation as a worker on the same desk. The control plane never touches Docker. A worker never touches Postgres or the object store.

That sandbox is the boundary around the job. It is not a boundary against the host kernel: a podium-node holds the Docker socket, which is root-equivalent on that machine. Run workers on machines that do nothing else, and read docs/security.md before you pick one.

The pieces

podium-server The control plane. API, scheduler, node registry, secrets, log ingest, and the web UI. Needs Postgres. An S3-compatible store keeps artifacts and rolled-up logs.
podium-agent The conductor. One assistant for the organization, the playbooks behind it, and the record of every turn. It is an API client of the server: its own database, its own token, and it never touches Docker. See docs/agent.md.
podium-node One per worker. Runs tasks on the local Docker engine. Root-equivalent on its host.
podium The CLI. Submits and follows tasks, and exits with the task's own exit code. Talks only to the server.
podium-runner PID 1 inside every task container. Forwards signals, reaps orphans, reports events. Embedded in podium-node; never installed by hand.

The same workers also run an ordinary container task (a build, a batch, a sidecar database) with no agent in it. podium run is that path. The agent layer is what turns a conversation into those tasks and brings the result back.


Deploy

No tagged release yet. There is no binary to download and no image to pull until a v* tag is pushed. Until then, build the images and set PODIUM_IMAGE_REPO to a registry you can reach. The recipe is in docs/quickstart.md. Read docs/security.md before putting a deployment anywhere that matters.

A control plane on this machine. Workers anywhere that can already reach it: a LAN, a WireGuard mesh, a corporate VPN. The deployment is one compose file, deploy/docker-compose.host.yml, and one .env.

mkdir podium && cd podium
curl -fsSLo docker-compose.yml \
  https://raw.githubusercontent.com/podium-ade/podium/main/deploy/docker-compose.host.yml

docker run --rm -v "$PWD:/out" --user "$(id -u):$(id -g)" \
  ghcr.io/podium-ade/podium-server:latest \
  init --dir /out

init writes master.key and .env with fresh credentials, and never overwrites either. Fill PODIUM_SERVER, this machine's address on your network, for example http://10.8.0.2:8080. 0.0.0.0 is a bind address, not a URL.

docker compose up -d --wait

Linux Engine. On a Mac, clone the repo and make stack-up. The same .env either way.

That brings up Postgres, an object store, the control plane, the conductor, and the web UI on 8080. The Agent screen is the conductor, reverse-proxied behind the server so there is one origin and one login. No Go toolchain and no Node on the host. It fails closed: nothing has a default password.

A worker is the one thing that stays opt-in. It mounts the host's Docker socket. Without one, a task sits in queued and says why.

echo "PODIUM_NODE_ENROLL_TOKEN=$(docker compose run --rm cli \
  node enroll-token --label demo)" >> .env
docker compose --profile node up -d

The enrollment token is single-use. A worker on another machine is the same command pointed at PODIUM_SERVER. docs/node-setup.md.

A model key is set in the UI, on purpose, so it lands in the encrypted secret store instead of in docker inspect. Shared memory (Hindsight) comes up with the stack and waits for a key of its own:

echo 'PODIUM_MEMORY_LLM_API_KEY=...' >> .env
docker compose up -d

The full walkthrough (the master key, tearing it down, building the images) is docs/quickstart.md. Tailscale, if you want the control plane on a MagicDNS name with no shared token, is docs/networking.md.

Two credentials the conductor does not invent, and both are how the organization reaches it:

  • Slack, Linear, and the GitHub App are off until their tokens are set. Each is one group of variables in deploy/.env.example. The GitHub App is one you create in your own org. Podium does not ship a Marketplace listing.
  • The assistant, the process that answers a conversation and delegates machine work, runs when the conductor is started with its host runtime. The published podium-agent image is a static binary and runs every turn as a task instead. docs/agent.md is which one you have, and what each door (chat, Slack, Linear, GitHub) does with it.

The playbook directory the image ships is a starter that holds nothing. Replace it with your own (one profile.yaml, one file per playbook) and re-read it from the UI. No restart.


Documentation

Start here

Using it

  • docs/task-spec.md: every spec field: secrets, sidecars, readiness, limits, hardening, artifacts
  • docs/cli.md: every command, its exit codes and its streams
  • examples/: a task with a sidecar, secrets, limits, artifacts
  • examples/agent/: the starter profile a fresh install runs
  • profile/: the playbook that develops this repository

Running it

Internals


Configuration

Every daemon is configured entirely by environment, and one file is the whole of it: a .env beside the compose file. deploy/.env.example documents every variable, and go test ./deploy/... fails the build if the code reads one that file does not mention.

podium-server init writes the credentials. What you set beyond that falls into three tiers. deploy/README.md has the full version:

tier
Required to start minted by init PODIUM_LOCAL_TOKEN, PODIUM_PG_PASSWORD, PODIUM_S3_SECRET_KEY, PODIUM_AGENT_TOKEN. Fill PODIUM_SERVER yourself: this machine's address on your network
Unlocks a feature one variable each, and without it only that feature is off PODIUM_MEMORY_LLM_API_KEY (shared memory), PODIUM_NODE_ENROLL_TOKEN (a worker's first run), PODIUM_AGENT_SLACK_* / PODIUM_AGENT_LINEAR_API_KEY (those sources), the GitHub App under Access → Connections, TS_AUTHKEY + PODIUM_TAILNET (Tailscale)
Just config ports, intervals, models, poll rates, labels, base URLs, all defaulted PODIUM_IMAGE_TAG is the one to pin regardless: latest moves, and a control plane and worker from different releases can disagree about the wire

The model key for a turn is not in this file. Set it in the UI so it is stored encrypted.

Development

make build              # UI + runner-embed + all four binaries into bin/, host-native
make test               # unit tests
make test-integration   # + real Postgres and Docker via testcontainers
make e2e                # boots a full stack and drives the real CLI
make lint proto fmt
go build -tags noui ./...   # skip the embedded UI, no Node required

Stop any running podium-node before make test-integration or make e2e. Both suites start real nodes against the host's Docker engine, and a node claims containers by the podium.task label alone, with no node scoping. Each side reports the other's containers to its own control plane, which has never heard of them, and tears them down. You lose the test run and whatever the live node was running, and it looks like flakiness or memory pressure. It is not. (DOCKER_HOST or PODIUM_NODE_DOCKER_HOST pointed at a second engine separates them too, if you have one.)

See CONTRIBUTING.md.


Security

Read docs/security.md before deciding which machines run a node. The short version: a podium-node is root-equivalent on its host, and a task container is untrusted. Report a vulnerability privately. See SECURITY.md.

License

Podium is licensed under the Apache License, Version 2.0.

SPDX-License-Identifier: Apache-2.0

Directories

Path Synopsis
cmd
podium command
Command podium is the Podium CLI: it submits tasks, follows them, and inspects the control plane.
Command podium is the Podium CLI: it submits tasks, follows them, and inspects the control plane.
podium-agent command
Command podium-agent is the Podium conductor: it holds the bot's identity in Slack, turns a mention into one Podium task running the agent runtime image, relays what the agent says back into the conversation and records the turn.
Command podium-agent is the Podium conductor: it holds the bot's identity in Slack, turns a mention into one Podium task running the agent runtime image, relays what the agent says back into the conversation and records the turn.
podium-node command
Command podium-node is the Podium worker daemon: it enrolls with the control plane, holds one bidirectional stream to it, and runs assigned tasks as containers on the local Docker engine.
Command podium-node is the Podium worker daemon: it enrolls with the control plane, holds one bidirectional stream to it, and runs assigned tasks as containers on the local Docker engine.
podium-runner command
Command podium-runner is PID 1 inside a Podium task container.
Command podium-runner is PID 1 inside a Podium task container.
podium-sandbox-agent command
Command podium-sandbox-agent runs one Podium task inside a cloud sandbox.
Command podium-sandbox-agent runs one Podium task inside a cloud sandbox.
podium-server command
Command podium-server is the Podium control plane: the Connect API, the node streams and the scheduler.
Command podium-server is the Podium control plane: the Connect API, the node streams and the scheduler.
internal
agent
Package agent wires the conductor together: its own store, a Podium API client, the profile directory, the sources, and one HTTP server for the AgentService, health and metrics.
Package agent wires the conductor together: its own store, a Podium API client, the profile directory, the sources, and one HTTP server for the AgentService, health and metrics.
agent/api
Package api is the conductor's own HTTP surface: the AgentService handlers, the bearer that guards them, and the health and metrics endpoints.
Package api is the conductor's own HTTP surface: the AgentService handlers, the bearer that guards them, and the health and metrics endpoints.
agent/chat
Package chat is the web chat: the third conductor.Source, and the only one whose conversation Podium itself holds.
Package chat is the web chat: the third conductor.Source, and the only one whose conversation Podium itself holds.
agent/chathost
Package chathost runs the conductor's chat host: one opencode 2 service on the conductor's own machine that answers every assistant conversation, instead of a runtime and a harness started for each turn.
Package chathost runs the conductor's chat host: one opencode 2 service on the conductor's own machine that answers every assistant conversation, instead of a runtime and a harness started for each turn.
agent/conductor
Package conductor is the turn loop: an inbound message becomes one Podium task running the agent runtime image, what the task says is relayed back to where the message came from, and the turn is recorded.
Package conductor is the turn loop: an inbound message becomes one Podium task running the agent runtime image, what the task says is relayed back to where the message came from, and the turn is recorded.
agent/conductor/fakesource
Package fakesource is an in-memory conductor.Source: a channel of inbound events and an ordered record of everything the conductor said back.
Package fakesource is an in-memory conductor.Source: a channel of inbound events and an ordered record of everything the conductor said back.
agent/config
Package config is the whole of podium-agent's configuration.
Package config is the whole of podium-agent's configuration.
agent/connections
Package connections is the Slack and GitHub configuration an operator saves from Settings.
Package connections is the Slack and GitHub configuration an operator saves from Settings.
agent/ghreview
Package ghreview is the GitHub App as a conductor.Source: webhooks turned into inbound events, comments posted back as the App, and Slack threads that asked for the same pull-request review joined onto the same session.
Package ghreview is the GitHub App as a conductor.Source: webhooks turned into inbound events, comments posted back as the App, and Slack threads that asked for the same pull-request review joined onto the same session.
agent/github
Package github is the conductor's client of a GitHub App: it turns an App id and a private key into the short-lived installation tokens a turn clones, fetches and pushes with.
Package github is the conductor's client of a GitHub App: it turns an App id and a private key into the short-lived installation tokens a turn clones, fetches and pushes with.
agent/linear
Package linear is the bot's Linear identity: assigned issues polled into inbound events, and the five ways the conductor talks back.
Package linear is the bot's Linear identity: assigned issues polled into inbound events, and the five ways the conductor talks back.
agent/mcp
Package mcp is the conductor's registry of MCP servers: what one is, what a name may be, and which Podium secret and environment variable a server's credential travels in.
Package mcp is the conductor's registry of MCP servers: what one is, what a name may be, and which Podium secret and environment variable a server's credential travels in.
agent/memory
Package memory is the conductor's client of Hindsight, the agents' shared memory.
Package memory is the conductor's client of Hindsight, the agents' shared memory.
agent/notify
Package notify is how the conductor tells a person something outside any conversation: a Slack direct message, or an email.
Package notify is how the conductor tells a person something outside any conversation: a Slack direct message, or an email.
agent/podium
Package podium is the conductor's client of the Podium API.
Package podium is the conductor's client of the Podium API.
agent/profiles
Package profiles is the bot's identity and its playbooks: what a playbook is, how one is validated, and how the directory on disk (PODIUM_AGENT_PROFILE_DIR) is loaded.
Package profiles is the bot's identity and its playbooks: what a playbook is, how one is validated, and how the directory on disk (PODIUM_AGENT_PROFILE_DIR) is loaded.
agent/skills
Package skills is a third-party Agent Skill on its way into a task container: what a bundle is, how one is read off the conductor's own disk, and the guards it has to pass before any of it reaches a turn.
Package skills is a third-party Agent Skill on its way into a task container: what a bundle is, how one is read off the conductor's own disk, and the guards it has to pass before any of it reaches a turn.
agent/slack
Package slack holds the bot's Slack identity: a Socket Mode connection, mentions turned into inbound events, and the four ways the conductor talks back.
Package slack holds the bot's Slack identity: a Socket Mode connection, mentions turned into inbound events, and the four ways the conductor talks back.
agent/store
Package store is the conductor's own Postgres database (podium_agent): sessions, turns, the relay ledger and its settings.
Package store is the conductor's own Postgres database (podium_agent): sessions, turns, the relay ledger and its settings.
cli
Package cli is the podium command-line client.
Package cli is the podium command-line client.
ids
Package ids mints the lowercase, prefixed ULIDs Podium uses for every entity.
Package ids mints the lowercase, prefixed ULIDs Podium uses for every entity.
netlisten
Package netlisten opens Podium's TCP listeners as plain TCP.
Package netlisten opens Podium's TCP listeners as plain TCP.
node
Package node is the podium-node daemon: it holds the node's identity, keeps one bidirectional stream to the control plane, and turns each Assign into a container run whose events are batched, buffered and replayed until the server acks them.
Package node is the podium-node daemon: it holds the node's identity, keeps one bidirectional stream to the control plane, and turns each Assign into a container run whose events are batched, buffered and replayed until the server acks them.
node/docker
Package docker runs Podium tasks as containers on a local Docker engine and streams ordered lifecycle events for them.
Package docker runs Podium tasks as containers on a local Docker engine and streams ordered lifecycle events for them.
node/sandboxexec
Package sandboxexec runs tasks in cloud sandboxes for podium-node.
Package sandboxexec runs tasks in cloud sandboxes for podium-node.
node/sandboxexec/agentbin
Package agentbin carries podium-sandbox-agent inside podium-node, the way the docker executor carries podium-runner.
Package agentbin carries podium-sandbox-agent inside podium-node, the way the docker executor carries podium-runner.
proto/podium/v1
Package podiumv1 holds the generated podium.v1 wire types plus the hand-written log redaction helper below.
Package podiumv1 holds the generated podium.v1 wire types plus the hand-written log redaction helper below.
runner
Package runner is podium-runner: PID 1 of every Podium task container.
Package runner is podium-runner: PID 1 of every Podium task container.
sandbox
Package sandbox is the provider-neutral face of a cloud sandbox: a Linux machine or container somebody else runs, that podium-node can create, put files in, start one process in, reach one port of, and delete.
Package sandbox is the provider-neutral face of a cloud sandbox: a Linux machine or container somebody else runs, that podium-node can create, put files in, start one process in, reach one port of, and delete.
sandbox/boat
Package boat is the boat.dev sandbox provider: each sandbox is a VM with Docker, made from boat's base image or from a named snapshot, driven over https://boat.dev/api/v1.
Package boat is the boat.dev sandbox provider: each sandbox is a VM with Docker, made from boat's base image or from a named snapshot, driven over https://boat.dev/api/v1.
sandbox/modal
Package modal is the Modal sandbox provider: each sandbox is a Modal Sandbox in one App, made from a registry image or a filesystem snapshot, driven with Modal's Go SDK.
Package modal is the Modal sandbox provider: each sandbox is a Modal Sandbox in one App, made from a registry image or a filesystem snapshot, driven with Modal's Go SDK.
sandbox/sandboxtest
Package sandboxtest has a sandbox provider backed by the local Docker engine, and the conformance suite every provider has to pass.
Package sandboxtest has a sandbox provider backed by the local Docker engine, and the conformance suite every provider has to pass.
sandboxagent
Package sandboxagent is podium-sandbox-agent: the process podium-node starts inside a cloud sandbox.
Package sandboxagent is podium-sandbox-agent: the process podium-node starts inside a cloud sandbox.
sandboxagent/wire
Package wire is the protocol between podium-node and the agent it runs inside a cloud sandbox: the run request, and the frames the agent streams back over a WebSocket.
Package wire is the protocol between podium-node and the agent it runs inside a cloud sandbox: the run request, and the frames the agent streams back over a WebSocket.
server
Package server wires the control plane together: store, transport, Connect handlers, the scheduler and the log fan-out, behind one HTTP server.
Package server wires the control plane together: store, transport, Connect handlers, the scheduler and the log fan-out, behind one HTTP server.
server/api
Package api holds the operator-facing Connect handlers: tasks and node administration.
Package api holds the operator-facing Connect handlers: tasks and node administration.
server/artifacts
Package artifacts is the control plane's half of the object store: the S3 client, the key layout, presigned URLs, and the service that records what has been stored.
Package artifacts is the control plane's half of the object store: the S3 client, the key layout, presigned URLs, and the service that records what has been stored.
server/artifacts/fakes3
Package fakes3 is an in-process S3-compatible endpoint for Podium's own tests.
Package fakes3 is an in-process S3-compatible endpoint for Podium's own tests.
server/auth
Package auth is Google Workspace sign-in and the instance claim that hangs off it.
Package auth is Google Workspace sign-in and the instance claim that hangs off it.
server/logs
Package logs owns the write side of a task's event history: it turns a node's event batch into rows, applies the status transitions those events imply, and fans the result out to live subscribers.
Package logs owns the write side of a task's event history: it turns a node's event batch into rows, applies the status transitions those events imply, and fans the result out to live subscribers.
server/nodes
Package nodes owns node identity and the node stream: enrollment, the in-memory session registry, heartbeat bookkeeping and the event batches nodes push back.
Package nodes owns node identity and the node stream: enrollment, the in-memory session registry, heartbeat bookkeeping and the event batches nodes push back.
server/sandboxes
Package sandboxes runs cloud sandboxes for podium-server.
Package sandboxes runs cloud sandboxes for podium-server.
server/sandboxsnaps
Package sandboxsnaps keeps the session workspaces a sandbox provider stores as images of its own (a Modal directory snapshot) instead of as tars in the object store.
Package sandboxsnaps keeps the session workspaces a sandbox provider stores as images of its own (a Modal directory snapshot) instead of as tars in the object store.
server/scheduler
Package scheduler decides which node runs which task, and keeps the promises that decision makes: a lease per assignment, a deadline for accepting one, the spec's own timeout, and the node-health policy that turns a machine going away into either a new attempt or an honest `lost`.
Package scheduler decides which node runs which task, and keeps the promises that decision makes: a lease per assignment, a deadline for accepting one, the spec's own timeout, and the node-health policy that turns a machine going away into either a new attempt or an honest `lost`.
server/secrets
Package secrets is the control plane's encrypted secret store: the master key, the AES-256-GCM envelope around every stored value, and the resolution step that turns a task's SecretRefs into the plaintext an Assign carries to a node.
Package secrets is the control plane's encrypted secret store: the master key, the AES-256-GCM envelope around every stored value, and the resolution step that turns a task's SecretRefs into the plaintext an Assign carries to a node.
server/snapshots
Package snapshots stores one workspace tar per conversation, and one base tar per repository, in the same S3 bucket artifacts use.
Package snapshots stores one workspace tar per conversation, and one base tar per repository, in the same S3 bucket artifacts use.
server/store
Package store is the only place SQL lives.
Package store is the only place SQL lives.
transport
Package transport is the seam between how a request reaches the server and who the server thinks made it.
Package transport is the seam between how a request reaches the server and who the server thinks made it.
transport/local
Package local is the single-machine transport: no Tailscale, no mTLS, one shared bearer token.
Package local is the single-machine transport: no Tailscale, no mTLS, one shared bearer token.
transport/tailnet
Package tailnet is the Tailscale transport.
Package tailnet is the Tailscale transport.
version
Package version carries the build identity of every Podium binary.
Package version carries the build identity of every Podium binary.
pkg
spec
Package spec defines the public task specification: the YAML/JSON document an operator writes and the validated Go type every Podium component codes against.
Package spec defines the public task specification: the YAML/JSON document an operator writes and the validated Go type every Podium component codes against.
Package web carries the built single-page UI into the server binary.
Package web carries the built single-page UI into the server binary.

Jump to

Keyboard shortcuts

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