How Shipmate works, and how to run it.
One page for the operator who installs it and the reviewer who signs it off.
From message to verified change
Shipmate is one long-lived agent per company, running on Anthropic’s Claude models in a container you host. Every chat message, scheduled check and CI call becomes a turn in one queue, so the agent that watched a deploy in one channel can connect it to a latency question in another.
| Step | What happens | Held in place by |
|---|---|---|
| Arrive | Someone on the allowlist mentions Shipmate or sends it a DM. The gateway reacts 👀 the moment the message is queued. | Per-platform allowlists that fail closed: an empty list means nobody. |
| Queue | Chat messages, scheduled checks and CI triggers line up for the one agent session, one turn at a time. | Gateway code, not the model. |
| Work | It reads your repos, logs and metrics and runs commands in its container, following a runbook where one fits. | The container it runs in and the credentials you mount. |
| Check | Every tool call passes Shipmate’s policy first: approval rules, verification, protection of its own config, and the audit log. | Code outside the model, reading config the agent can’t write. |
| Prove | After a deploy, apply, restart or merge, it verifies the change and reports pass, fail or skipped, with the evidence. | The verification gate: in production the next change waits for a pass. |
| Reply | It answers in the thread. The gateway adds the verdict line and swaps 👀 for ✅, or ⚠️ if the turn failed. | Gateway code. No reaction within a second means the agent is down. |
What it learns lives on a memory volume of its own: what is true now, the state of each system, its runbooks, incident write-ups and a monthly log (see How it improves itself). It writes them as it goes, so its context survives restarts and long sessions.
Before you start
- Host
- linux/amd64 Docker Compose on a VM of its own, or a Kubernetes cluster with Helm and a node pool of its own. Shipmate runs as root and owns that box.
- Model
- Anthropic’s Claude An Anthropic API key, or Claude through Amazon Bedrock, Google Vertex AI, Microsoft Foundry or your own LLM gateway. See Model access.
- Chat
- One platform A Slack app, Discord bot or Teams bot (preview) of its own. Never reuse another bot’s token.
- Access
- The least it needs Read access to your logs and metrics, a read-only kubeconfig, a Git token that can open pull requests but can’t push to protected branches, and permission to run your CI workflows. Keep production credentials in CI.
- Images
- ghcr.io/roundbeat-ai Released images and the Helm chart are private during early access, and the Compose setup runs from the Shipmate repository. We set up access to both with your team when you join.
Install with Docker Compose
Compose runs Shipmate and its console side by side on one host, from a checkout of the Shipmate repository. Run it from the repository root, because Compose reads the console’s token from the .env there.
# model access, the API token, chat tokens and allowlists
cp .env.example .env
docker compose up -d --build
curl http://127.0.0.1:8787/healthz
# then open the console at http://localhost:5273
The least .env needs before the first start:
| Variable | What it’s for |
|---|---|
ANTHROPIC_API_KEY | Model access, or your cloud provider’s settings instead (see Model access). |
SHIPMATE_API_TOKEN | Required: in a container the gateway won’t start without it. It protects the console and the management API. Make one with openssl rand -hex 24. |
SHIPMATE_AGENT_NAME | Your teammate’s name, shown in chat and the console. |
- Both ports bind to
127.0.0.1only:8787is the management API and5273the console. Your team talks to Shipmate in chat, not over HTTP. - Shipmate runs as root on a writable filesystem and owns its container: it installs the tools it needs. What should outlast a restart lives on the workspace volume (see How it improves itself).
- Memory lives in the
shipmate-memoryvolume, and scheduled checks, tools and the audit log inshipmate-workspace.docker compose down -vresets the agent, memory included. - Don’t run a second copy against the same chat app: two instances split the incoming messages between them.
Install on Kubernetes
The Helm chart runs one agent per namespace. Secrets come from a Secret you create; the chart never puts them in values. Use the release version we give you for <X.Y.Z>.
# add SLACK_BOT_TOKEN and SLACK_APP_TOKEN, SHIPMATE_DISCORD_TOKEN,
# SHIPMATE_TEAMS_APP_PASSWORD or SHIPMATE_TRIGGER_TOKEN as you need them
kubectl create secret generic shipmate-secrets \
--from-literal=ANTHROPIC_API_KEY=sk-ant-... \
--from-literal=SHIPMATE_API_TOKEN=$(openssl rand -hex 24)
# the images are private during early access
kubectl create secret docker-registry ghcr-pull \
--docker-server=ghcr.io --docker-username=<user> \
--docker-password=<token with read:packages>
helm registry login ghcr.io -u <user>
helm install shipmate oci://ghcr.io/roundbeat-ai/charts/shipmate \
--version <X.Y.Z> \
--set existingSecret=shipmate-secrets \
--set 'imagePullSecrets[0].name=ghcr-pull' \
--set 'allowedUsers={U0123ABCD}'
| Value | Default | What it does |
|---|---|---|
agentName | Shipmate | The teammate’s name. |
model | "" | A Claude model ID. Empty uses the default model. |
opsChannel | ops | Where escalations and CI alerts post, until someone runs sethome. |
allowedUsers | [] | Slack member IDs who may talk to it. Empty means nobody. |
discord.* | [] | appId, allowedUsers and an optional channels pin. Quote Discord IDs: YAML would round them into a different number. |
teams.* | enabled: false | Teams (preview): appId, tenantId, allowedUsers, and the port your Ingress routes to. |
agentConfig.overridesConfigMap | "" | A ConfigMap holding your policy.json and triggers.json, mounted read-only over the defaults. |
triggers.ingress.enabled | false | An Ingress that exposes only /hooks, so CI outside the cluster can reach it. |
networkPolicy.enabled | true | Limits who can reach the agent. Egress stays open: it needs the model, chat and your tooling. |
persistence.size | 5Gi | The workspace: scheduled checks, tools, setup.sh and the audit log. |
persistence.memory.* | 1Gi | Memory on a claim of its own at /workspace/memory, or your own claim with existingClaim. |
replicaCount | 1 | One agent is one session. The chart refuses any other number. |
The pod runs as root on a writable filesystem, with the default seccomp profile and no privilege escalation; give it a node pool of its own. The chart doesn’t include the console; it’s part of the Compose setup.
Model access
Shipmate runs on Anthropic’s Claude models, through an account you own. You pay your provider directly, and the console shows what the agent has spent.
- API key
- ANTHROPIC_API_KEY The usual choice. Use an API key whenever more than one person talks to the agent: a personal Claude subscription login isn’t allowed in a shared deployment.
- Your cloud
- Bedrock, Vertex AI or Foundry Claude through Amazon Bedrock, Google Vertex AI or Microsoft Foundry, for your own billing and data residency. Set that provider’s variables from
.env.example. - Your gateway
- ANTHROPIC_BASE_URL Point Shipmate at your LLM gateway’s Anthropic-compatible endpoint.
- Which model
- SHIPMATE_MODEL Any Claude model. Set it in
.env, the Helmmodelvalue, or the console’s Lines view.
Connect your chat
Connect the platform your team already uses. One platform per deployment is the default. Every channel and DM on it feeds the same agent and the same memory, so connect internal channels only.
Slack
Slack connects over Socket Mode, so Shipmate needs no public URL.
- At api.slack.com/apps, choose Create New App, then From an app manifest, and paste the manifest below.
- Under Basic Information, App-Level Tokens, generate a token with the
connections:writescope. Thatxapp-…token isSLACK_APP_TOKEN. - Under OAuth & Permissions, install the app to your workspace. The Bot User OAuth Token (
xoxb-…) isSLACK_BOT_TOKEN. - Put your member ID (Profile, then ⋯, then Copy member ID) in
SHIPMATE_ALLOWED_USERS. Add teammates comma-separated. - Recreate the container, invite the bot to your ops channel with
/invite @shipmate, then say@shipmate sethomethere.
display_information:
name: Shipmate
description: DevOps teammate in your chat
features:
bot_user:
display_name: shipmate
always_online: true
oauth_config:
scopes:
bot:
- app_mentions:read
- channels:history
- channels:read
- groups:history
- groups:read
- chat:write
- im:history
- im:read
- im:write
- reactions:read
- reactions:write
settings:
event_subscriptions:
bot_events:
- app_mention
- message.im
- message.channels
- message.groups
interactivity:
is_enabled: true
socket_mode_enabled: true
On Slack, once Shipmate has replied in a thread you can keep talking there without mentioning it. It never answers general channel chatter.
Discord
Discord connects out over Discord’s gateway, so it needs no public URL either.
- Create a new application at discord.com/developers/applications and copy its Application ID.
- Under Installation, set Install Link to None. Under Bot, reset and copy the token, turn Public Bot off, and turn on the Message Content and Server Members intents.
- Invite it with
https://discord.com/oauth2/authorize?client_id=<APPLICATION_ID>&scope=bot%20applications.commands&permissions=309237746752. That allows viewing channels, sending messages and threads, reading history, adding reactions and attaching files. Nothing administrative. - With Developer Mode on (User Settings, Advanced), right-click your name and copy your user ID for the allowlist.
- Set the variables below, recreate the container, then say
@Shipmate sethomein your ops channel.
SHIPMATE_DISCORD_TOKEN=<bot token>
SHIPMATE_DISCORD_APP_ID=<application id>
# who may talk to it; empty means nobody
SHIPMATE_DISCORD_ALLOWED_USERS=123456789012345678
# optional: only these channels (DMs from allowed users always work)
SHIPMATE_DISCORD_CHANNELS=
On Discord, every message must mention Shipmate or be a DM, replies in its own threads included. Tables arrive as aligned code blocks and charts as a table of the data, because Discord has neither natively.
Microsoft Teams Preview
Teams pushes messages in, so it’s the one platform that needs a public HTTPS endpoint.
- Create an Azure Bot. Note its Microsoft App ID, create a client secret, and note the tenant ID if the bot is single-tenant.
- Set
SHIPMATE_TEAMS_APP_ID,SHIPMATE_TEAMS_APP_PASSWORDandSHIPMATE_TEAMS_TENANT_ID, then recreate the container. Compose publishes the Teams port on127.0.0.1:3978. - Put a tunnel such as Cloudflare Tunnel or Tailscale Funnel in front of that port only, and set the bot’s messaging endpoint to
https://<your-host>/api/messages. - Build the app in the Teams Developer Portal with your App ID, then upload it to Teams.
- Message it once. The log names your Teams ID as ignored: add that ID to
SHIPMATE_TEAMS_ALLOWED_USERSand recreate the container.
Every request must carry a Microsoft-signed token issued for your App ID; anything else gets a 401. On Kubernetes, set teams.enabled, teams.appId and teams.tenantId, and route an Ingress to the Teams port.
Check it’s connected
docker logs shipmate 2>&1 | grep '\[chat\]'
Then mention Shipmate in your ops channel. You should see 👀 at once, a threaded reply, then ✅. No 👀 at all usually means your ID isn’t on the allowlist yet: the log names the ID it ignored.
Teach it your setup
Say @Shipmate interview us, or ask it to learn how you deploy. It reads your repos first (CI, manifests, CODEOWNERS and docs), then asks one short round of at most six questions, each with its best guess. The same day it opens an AGENT-GUIDE.md in each repo as a pull request: how you deploy, how to verify a change, and where your logs and metrics live.
A repo’s AGENT-GUIDE.md overrides the runbooks’ defaults. To add a procedure of your own, ask it to write down how you do something: it adds the steps under ## Runbooks in that repo’s guide, by pull request, and follows them once merged. The built-in runbooks can’t be changed from chat, but it writes runbooks of its own (next section).
Where your process lacks a common practice, such as releasing to every host at once, checking only /health or having no rollback plan, it says so once with a concrete suggestion. Then it follows your way unless you agree to change it.
How it improves itself
Shipmate runs as root and owns its container, so it installs what a job needs. Anything outside its volumes is gone after a restart, so it writes down what it learns. Its memory follows the layout our own production agent settled on, and only MEMORY.md is seeded: Shipmate creates the rest as it learns.
| In memory/ | What it’s for |
|---|---|
MEMORY.md | What is true now: people and who approves what, standing rules (each with who said it and when), access by name only, channels, the fleet and runbook indexes, open items, and the gotchas that keep biting. Edited in place, never a log, and kept under the 200 lines loaded every session. |
fleet/ | The current state of each system: hosts, versions, pins, the config that matters. Every fact has an “as of” date and its source, and a changed fact is updated in place with the old value logged. |
runbooks/ | Procedures it writes the second time it does something non-trivial, or when you ask it to. Each opens with who approves and when it was last validated, and it fixes them the moment a step turns out wrong. |
incidents/ | A write-up after every outage or near-miss: timeline, root cause, evidence, fix, what’s still open and lessons. |
archive/ | An append-only log, one file a month: what it did, deploys, decisions and every superseded fact. |
Memory has a volume of its own, shipmate-memory in Compose and a second claim in the Helm chart, so it outlives a workspace reset and backs up on its own. It holds no secrets: writes that look like one are refused. It’s versioned after every turn, so you can see what changed. Rules and authority change only on the word of the person who holds them; Shipmate records decisions, it doesn’t make them.
| In the workspace | What it’s for |
|---|---|
bin/ | Scripts and small tools, on its own PATH. |
setup.sh | Every install it needs, replayed in the background at each start. Progress is in .shipmate/setup.log and the result in .shipmate/setup.status; SHIPMATE_SETUP_TIMEOUT caps it at 900 seconds by default. |
Procedures that belong with one repo go into that repo’s AGENT-GUIDE.md by pull request instead, where your team reviews them.
Runbooks
The repeatable parts of ops work ship as runbooks that Shipmate follows instead of improvising. They carry general DevOps practice, and your AGENT-GUIDE.md adapts them to your team.
| Runbook | Asked as | What it does |
|---|---|---|
rollout | “ship X to prod” | Releases through your pipeline with your strategy: rolling, canary, blue/green or flags. It builds once and promotes the same artifact, sends one slice first and proves it against your SLIs and the stable fleet before the rest. It needs no shell on a production host. |
incident-check | “is this hitting us?” | Maps the incident to your dependencies and measures traffic, errors and latency against a baseline, with a non-zero control. The verdict comes first. If there’s impact it mitigates before root-causing, keeps a UTC timeline, and offers a blameless postmortem afterwards. |
change-via-pr | any infra or config change | The smallest diff on a fresh branch, with the rendered plan in the PR, the blast radius and the rollback. Nothing merges until the team says so; then it checks before and after, neighbours included. |
upgrade-watch | upgrades, end-of-life dates, go-lives | Tracks versions, end-of-life dates and certificate expiry. It upgrades non-production first, takes backups before the point of no return, checks right after activation, then stands down. |
verify | after every change | A functional check against the baseline and your SLOs over a watch window, reported as pass, fail or skipped with the evidence. |
onboarding-interview | joining a team | Repos first, then at most six questions, then an AGENT-GUIDE.md per repo by pull request. |
Approvals
Some commands should never run without a person’s go, a production deploy for example. Add rules to approvals.rules in policy.json, which the agent can’t write. With Compose, edit runner/agent-config/policy.json and rebuild; on Kubernetes, put it in the overrides ConfigMap.
"approvals": {
"rules": [{
"name": "prod deploy",
"patterns": ["inventory[/=]\\S*prod", "prod-cluster", "deploy-prod"],
"approvers": { "slack": ["U0123ABCD"], "discord": ["123456789012345678"] },
"timeoutSec": 900
}]
}
- When the agent runs a matching command, an Approve / Hold card appears in the thread. Secret-looking values in the command are masked.
- The command waits. Only a listed approver’s click counts; anyone else gets a private note.
- Approve runs it. Hold, or no answer before the timeout, means it doesn’t run, and the agent is told not to retry or work around it.
- The card shows who decided, and every decision goes to the audit log and to
shipmate_approvals_total.
Scheduled checks and CI turns have no thread to ask in, so matching commands are refused there. The turn timeout pauses while a person decides.
Admin mode
During an incident or a planned migration you may want the agent to just get on with it. Admin mode is a time-boxed switch with two levels.
| Level | Say in chat | What it does |
|---|---|---|
| Approvals | @Shipmate admin on 30m | Commands that match an approval rule run without asking, each leaving a note in its thread. The rest of the policy holds. |
| Bypass | @Shipmate admin bypass 30m | No policy check at all; every call runs. Only the container remains as a boundary. |
@Shipmate admin offends it early, and@Shipmate adminshows the state.admin ondefaults to an hour; bypass always needs a duration. Both are capped at 8 hours (approvals.adminMode.maxMinutes, at most 24).- Only the people in
approvals.adminMode.approverscan switch it in chat. If that isn’t set, only people who approve in every rule can, so someone who approves only staging can’t lift the production gate. - Operators can also switch it from the console’s Seals view. The agent can’t: it never holds the management token.
- Every switch and expiry is posted to the home channel and written to the audit log, and the console shows a red band while it’s on. A restart ends it.
- To forbid it, set
"adminMode": {"allowed": false}inapprovals. To allow only the approvals level, set"allowBypass": false.
Verification
No change is done until it’s verified. The gateway, not the model, tracks every change and decides the outcome.
A change is a deploy, apply, rollout, restart or merge: ansible-playbook without --check, kubectl apply, patch, scale or rollout restart, helm upgrade, install or rollback, terraform apply, docker compose up, systemctl restart, argocd app sync and gh pr merge. Set verification.changes in policy.json to replace the list.
After each change the agent verifies it with a real functional request (not just /health), error rate and latency against the pre-change baseline, rollout and load-balancer state, and a long enough watch window. Then it reports pass, fail or skipped with the evidence, batch by batch. What happens next depends on the environment’s mode; production matches prod by default.
| Mode | Default for | What the gateway does |
|---|---|---|
| enforce | production | The next change there is refused until the previous one passes, so rollouts are verified batch by batch. After a failed check, one change may run (the rollback or fix), and it must pass before anything else. |
| warn | everything else | The reply ends “unverified” instead of “verified”. |
| advise | — | Suggestions only. |
| off | — | Not tracked. |
The reply’s last line comes from the gateway, never the model:
| Mark | Means |
|---|---|
| ✅ | Verified. The changes in that turn passed their checks. |
| ⚠️ | Failed. A check failed. In production only the rollback or fix may run next. |
| ⛔ | Blocked. A production change hasn’t passed its check, so the next change there is refused. |
| ❔ | Unverified. A change outside production ran without a passing check. |
@Shipmate verify status shows what’s waiting, and @Shipmate verify skip <reason> lifts a block. Only admin-mode approvers can skip, or anyone allowed when none are configured, and the skip lands in the audit log with the reason.
Scheduled checks
Scheduled checks live in crons.md in the agent’s workspace, one section per check. The scheduler fires each one into the session when it’s due and records when it ran and how it went. Silence means healthy: a run posts to the home channel only when it escalates, or when it fails to report a result.
## cron-002: DB disk headroom
- **interval**: 1h
- **enabled**: true
- **task**: Check free disk on the database hosts from the node exporter metrics.
- **escalate_if**: Any volume under 15% free, or the metric is missing.
- **last_run**: never
- **last_status**: never run
- Ask Shipmate to add or change a check in plain words, or edit the file. Pause and resume them from the console’s Ledger view.
- Intervals take
30s,5m,2hor1d. Each run is a full model turn, so match the interval to how fast the thing changes: a 5-minute check is twelve turns an hour. - Keep titles short. The console shows them, capped at 60 characters.
CI triggers
Pipelines call Shipmate over an authenticated webhook, POST /hooks/<name>. The task comes from triggers.json, which you own and the agent can’t write; deploy-finished and pipeline-failed ship as examples. The caller only fills in the fields a trigger declares, and those reach the agent fenced as untrusted data, so a malicious PR title in a payload can’t rewrite the job.
"deploy-finished": {
"instructions": "A deployment just finished. Verify it with the verify skill: a functional request against the deployed service (not just /health), error rate and latency against how it looked before the deploy, rollout and load-balancer state, over a long enough window. Report it with report_verification (pass, fail or skipped, with the evidence) and say clearly whether the deploy is healthy.",
"fields": ["service", "environment", "version", "run_url"],
"channel": "ops",
"notify": "escalate"
}
With ?wait=1 the call blocks and returns {"status": "ok" | "escalate" | "failed", "summary", "reply"}, so a pipeline can gate on it. This GitHub Actions step fails the job if the check escalates:
- name: Shipmate post-deploy check
env:
SHIPMATE_URL: ${{ secrets.SHIPMATE_URL }}
SHIPMATE_TRIGGER_TOKEN: ${{ secrets.SHIPMATE_TRIGGER_TOKEN }}
run: |
curl -fsS -X POST "$SHIPMATE_URL/hooks/deploy-finished?wait=1" \
-H "Authorization: Bearer $SHIPMATE_TRIGGER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"service":"api","environment":"prod","version":"${{ github.sha }}"}' \
| tee result.json | jq -e '.status == "ok"'
Set SHIPMATE_TRIGGER_TOKEN to turn triggers on. notify: "escalate" posts to chat only when something is wrong; "always" posts every result. CI has to reach the gateway: on Kubernetes enable triggers.ingress, which exposes only /hooks; on a single host put a tunnel in front of port 8787 for the /hooks path only.
The console
The console is the operator’s control room, at http://localhost:5273 in the Compose setup. It runs in its own container, serves the interface, and forwards only the management API and the health check to the agent, adding the token on the server side.
| View | What it shows |
|---|---|
| Clearance | One verdict stamp, what’s wrong and the next step, then status, queue, turns, errors, cost and uptime. Restart lives here. |
| Ledger | Scheduled checks with their last result, and pause or resume. |
| Lines | Chat platforms, model access, the agent’s name, model and home channel. |
| Seals | The data boundary with the rule that enforces each seal, policy decision counts, and the admin-mode switch. |
The console can’t read what the agent knows, and that’s enforced in the API rather than hidden in the interface. No management route returns messages, replies, memory, command text, check output or tokens; it gets configuration, states, counts, timings and costs. Its server can’t reach the chat or automation endpoints at all.
Monitoring
GET /metrics on port 8787 serves Prometheus text: counts, timings and states only, never message text, memory, commands or check output. Send SHIPMATE_API_TOKEN as a bearer token.
| Metric | What it tells you |
|---|---|
shipmate_up, shipmate_heartbeat_timestamp_seconds | Is it alive? The heartbeat runs every minute without a model call. |
shipmate_queue_depth, shipmate_current_turn_seconds | Is it keeping up, or is a turn stuck? |
shipmate_turns_total{kind,outcome} | Chat, scheduled and trigger turns, and how many failed. |
shipmate_tokens_total, shipmate_model_cost_usd_total | Tokens and spend per model. Restarts don’t re-count them. |
shipmate_tool_calls_total{tool,decision} | Policy decisions per tool. |
shipmate_cron_runs_total, shipmate_trigger_runs_total | Scheduled check and automation outcomes. |
shipmate_unverified_changes | Changes still waiting on a passing check. |
Ready-made alert rules ship in deploy/prometheus/shipmate-alerts.yml: down, stale heartbeat, chat disconnected, stuck turn, backlog, failing turns or checks, and daily spend. Route them through something other than Shipmate, because it can’t tell you it’s down. With the API token set, /metrics answers on any hostname, so a scraper reaching it by name needs nothing more.
Security model
The box is the boundary. The agent’s shell is open, because ops work needs kubectl, jq and curl, and string rules on commands can always be phrased around, so Shipmate doesn’t pretend they’re a boundary. What limits it:
- The VM it runs in. Shipmate runs as root and owns its container, so the container is its workspace, not a wall. Give it a VM or node pool of its own, with nothing else on it.
- The credentials you mount. It can do what they allow and nothing more. Keep production credentials out of its reach.
- Branch protection on your repos. Pull-request-only is enforced by your Git host: protect
mainand give it a token without admin rights. - A scrubbed environment. Its shell is handed an allowlisted set of variables: chat tokens aren’t passed to it, and the API and trigger tokens exist only as hashes. As root it could still read the gateway’s own environment.
- HTTP guards. Cross-site requests and unexpected Host headers are refused, bodies must be JSON and are capped at 1 MB, and the API token protects the management API.
- Untrusted input stays data. Trigger fields are declared, capped and fenced, and the agent is told to treat logs, web pages and event data as data, never as instructions.
Shipmate’s own gates run on every tool call. They’re about people and workflow:
| Gate | What it does |
|---|---|
| Approvals | Matching calls wait for a named approver in the thread. |
| Verification | In enforce environments the next change waits until the previous one passes. |
| Admin mode | Approvers can skip approvals, or every check, for a set time. |
| Memory guard | Writes into its memory can’t carry secrets, MEMORY.md stays under the 200 lines that load, and the monthly log is append-only. |
| Its own config | Its file tools can’t write the persona, policy, triggers, built-in runbooks, Shipmate’s own code or its runtime state. |
| Audit log | Every tool call and its decision is appended to audit.log in the workspace. |
Grant access in tiers, and stop at the one the job needs:
| Tier | Access | Enforced by |
|---|---|---|
| Observer | Observability read-only, and chat | The credentials you mount and the chat allowlist. |
| Investigator | Plus repo read and Kubernetes read-only | Read-only RBAC or kubeconfig, and a read-only Git token. |
| Collaborator | Plus repo write, pull requests only | Branch protection, and a Git token that can’t push to protected branches. |
| Operator | Plus running your pipelines | Your existing pipelines, plus approvals and verification. |
Chat commands
The gateway handles these itself, before the model sees anything. Everything else is plain language.
| Say | Who | What happens |
|---|---|---|
@Shipmate sethome | anyone allowed | Makes this channel the home channel, where escalations and CI alerts post. A DM works too. |
@Shipmate admin on 30m | admin approvers | Commands matching an approval rule run without asking, for the time given. |
@Shipmate admin bypass 30m | admin approvers | Turns off every policy check for the time given. |
@Shipmate admin off | admin approvers | Ends admin mode early. @Shipmate admin shows the state. |
@Shipmate verify status | anyone allowed | Shows which changes are waiting on a check. |
@Shipmate verify skip <reason> | admin approvers, or anyone allowed if none are set | Lifts a verification block, recorded in the audit log with the reason. |
Environment variables
Set these in .env for Compose. On Kubernetes, tokens go in the Secret and the rest have Helm values.
| Variable | What it’s for |
|---|---|
ANTHROPIC_API_KEY | Model access. See Model access for your cloud or gateway. |
SHIPMATE_API_TOKEN | Required in a container. Protects the console, the management API and /metrics. |
SHIPMATE_AGENT_NAME | The teammate’s name. |
SHIPMATE_MODEL | The Claude model to run. |
SHIPMATE_OPS_CHANNEL | The home channel’s ID, if you set it up front. sethome or the console take precedence. |
SLACK_BOT_TOKEN, SLACK_APP_TOKEN | Slack’s bot token (xoxb-) and app-level token (xapp-). |
SHIPMATE_ALLOWED_USERS | Slack member IDs who may talk to it, comma-separated. Empty means nobody. |
SHIPMATE_DISCORD_TOKEN, SHIPMATE_DISCORD_APP_ID | The Discord bot’s token and application ID. |
SHIPMATE_DISCORD_ALLOWED_USERS, SHIPMATE_DISCORD_CHANNELS | Discord user IDs who may talk to it, and an optional channel pin. |
SHIPMATE_TEAMS_APP_ID, SHIPMATE_TEAMS_APP_PASSWORD, SHIPMATE_TEAMS_TENANT_ID | The Azure Bot’s app ID, client secret and tenant (preview). |
SHIPMATE_TEAMS_ALLOWED_USERS, SHIPMATE_TEAMS_PORT | Teams user IDs who may talk to it, and the port Teams posts to (3978). |
SHIPMATE_TRIGGER_TOKEN | Turns on CI triggers and authenticates them. |
SHIPMATE_SETUP_TIMEOUT | How long the start-up replay of setup.sh may run, in seconds (900 by default). |
SHIPMATE_ALLOWED_HOSTS | Extra hostnames the management API answers to, when you reach it by a name other than localhost. |
Releases and upgrades
Releases are versioned, and there’s no :latest: every install pins a version, or better, its digest. The shipmate and shipmate-console images are linux/amd64, carry an SBOM and build provenance, and are signed keylessly by the release workflow.
cosign verify ghcr.io/roundbeat-ai/shipmate:<X.Y.Z> \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp \
'^https://github.com/RoundBeat-Ai/shipmate/\.github/workflows/release-please\.yml@'
docker buildx imagetools inspect ghcr.io/roundbeat-ai/shipmate:<X.Y.Z> --format '{{ json .SBOM }}'
helm upgrade shipmate oci://ghcr.io/roundbeat-ai/charts/shipmate \
--version <X.Y.Z> --reuse-values
The agent’s memory, scheduled checks and audit log live on its volumes, so an upgrade keeps them. Upgrading from a release before the memory volume? Copy memory/ into the new volume first; the README shows how. If the agent’s instructions changed in the new release, it starts a fresh session and carries on from its memory.