Patterns — three worked examples¶
Three concrete integrations, end to end, showing how the pieces in this section fit together. These are sketches that show the shape — adapt them to your stack. Where a recipe touches a specific request/response field, match the names in api-recipes.md (they're taken from the handler structs).
Pattern 1: Slack approval pinger¶
Goal: every time an agent emits /request-approval, post a message in
#trimus-approvals so a human can resolve it without opening Trimus.
Pieces: SSE stream (sse-streaming.md), JSON API, Slack webhook.
[ trimus-server ] ──SSE──→ [ pinger ] ──webhook──→ [ Slack ]
│
└─ on click → [ Slack action ] → PATCH /api/approvals/{id}
import requests, sseclient # any SSE client works
BASE = "https://trimus.example.com"
TOKEN = "sk_..." # board-user key for a "slack-bridge" service account
SLACK = "https://hooks.slack.com/services/..."
# The approval_created event's data is the bare approval ID.
stream = sseclient.SSEClient(
f"{BASE}/api/live/events",
headers={"Authorization": f"Bearer {TOKEN}"})
for event in stream:
if event.event != "approval_created":
continue
approval_id = event.data
a = requests.get(f"{BASE}/api/approvals/{approval_id}",
headers={"Authorization": f"Bearer {TOKEN}"}).json()
requests.post(SLACK, json={
"text": (f":rotating_light: New approval pending\n"
f"*{a['title']}*\n"
f"<{BASE}/approvals/{approval_id}|Resolve in Trimus>")})
What you need on the Trimus side¶
- A board-user account for the bridge (use a board-user or user key,
not an agent key — only humans can resolve approvals; the loop-back
PATCH /api/approvals/{id}needs that). - Run the pinger as a long-lived process. Reconnect with backoff and
resend
Last-Event-IDso a brief drop replays from the hub buffer (see sse-streaming.md).
Possible upgrades¶
- Slack action buttons that POST to a small handler which calls
PATCH /api/approvals/{id}with{"status":"approved","resolution":"…"}. - Ack-before-post: if losing a ping during a long outage is unacceptable, drive this from a worker (building-a-worker.md) instead of SSE.
- Filter by role: only ping for approvals from senior agents.
Pattern 2: GitHub PR → Trimus issue bridge¶
Goal: when a PR opens in acme/payments, create a Trimus issue for
the engineering-manager agent to review and route.
Pieces: GitHub webhook → a tiny signing relay → Trimus routine trigger (webhooks-in.md) → the agent creates the issue.
[ GitHub ] ──webhook──→ [ relay (verifies GitHub HMAC, re-signs for Trimus) ]
│
↓ POST .../fire (X-Staple-Signature)
[ routine fires the agent ]
│
↓
[ agent emits /create-issue ]
Doing the issue creation agent-side keeps the routing logic editable without redeploys and the relay dumb.
The Trimus side¶
- Create a routine ("github-pr-opened") targeting a triage or engineering-manager agent.
- Add a
webhooktrigger withsigning_mode:"hmac"(via the UI orPOST /api/routines/{routineId}/triggers). Copy thepublic_idand the one-timesecret. - Write the routine's prompt template to act on the payload:
A new GitHub PR opened.
Repo: {{.payload.repository.full_name}}
Title: {{.payload.pull_request.title}}
URL: {{.payload.pull_request.html_url}}
Author: {{.payload.pull_request.user.login}}
Body:
{{.payload.pull_request.body}}
Create a Trimus issue tracking this PR with /create-issue, then
/assign the appropriate engineer for this repo.
The relay (the important bit)¶
Trimus's webhook signature is hex(HMAC-SHA256(rawBody, secret)) in
X-Staple-Signature — it is not the raw secret, and it is not
GitHub's signature. GitHub signs with its own secret, so you need a
small relay that verifies GitHub's HMAC and re-signs the body for Trimus:
import hashlib, hmac, os, requests
from flask import Flask, request, abort
app = Flask(__name__)
GH_SECRET = os.environ["GITHUB_WEBHOOK_SECRET"]
TRIMUS_SECRET = os.environ["TRIMUS_TRIGGER_SECRET"]
FIRE_URL = os.environ["TRIMUS_FIRE_URL"] # .../routine-triggers/public/<public_id>/fire
@app.post("/github")
def github():
body = request.get_data() # raw bytes — sign exactly these
# 1. Verify GitHub's signature (its scheme: sha256=<hex>).
gh_sig = request.headers.get("X-Hub-Signature-256", "")
expect = "sha256=" + hmac.new(GH_SECRET.encode(), body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(gh_sig, expect):
abort(401)
# 2. Re-sign for Trimus and forward the SAME bytes.
trimus_sig = hmac.new(TRIMUS_SECRET.encode(), body, hashlib.sha256).hexdigest()
r = requests.post(FIRE_URL, data=body,
headers={"Content-Type": "application/json",
"X-Staple-Signature": trimus_sig})
return ("", r.status_code)
You can skip the relay only if you set the trigger to signing_mode:
"none" and lock the fire URL to GitHub's IP ranges at your proxy —
Trimus has no built-in IP allowlist, so network-layer scoping is how you'd
do it.
Possible upgrades¶
- Close the loop on merge: a second trigger for
pull_request.closedwhose routine sets the matching issue todone. - Two-way sync: mirror Trimus comments back to the PR via a comment-watcher SSE consumer.
Pattern 3: Daily cost report¶
Goal: every weekday at 09:00, post a short cost summary as a comment on a "Daily ops" issue.
Pieces: cron + JSON API. No SSE, no webhooks, no plugin.
#!/usr/bin/env bash
set -euo pipefail
BASE="https://trimus.example.com"; TOKEN="sk_..."
COMPANY="<uuid>"; DAILY_OPS_ISSUE="<uuid>"
# Cost rollup (the /costs response is aggregated per agent/model/day —
# total however your reporting needs; this just grabs the raw feed).
COSTS=$(curl -s -H "Authorization: Bearer $TOKEN" \
"$BASE/api/companies/$COMPANY/costs")
# Open budget incidents.
INCIDENTS=$(curl -s -H "Authorization: Bearer $TOKEN" \
"$BASE/api/companies/$COMPANY/budget-incidents?status=open" | jq 'length')
BODY=$(printf 'Daily ops summary:\n\n- Cost feed: see dashboard\n- Open budget incidents: %s\n' "$INCIDENTS")
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d "$(jq -n --arg b "$BODY" '{body_markdown: $b}')" \
"$BASE/api/issues/$DAILY_OPS_ISSUE/comments"
Possible upgrades¶
- Build the report inside an agent instead of shell: a routine fires daily at 09:00 with a "Daily Reporter" agent that reads the cost API itself and writes a richer report. Same outcome, more flexibility, and no token in a cron script.
- Cross-company: run per company, or (with instance-admin rights) walk the company list. Note each company still needs its own scoped key for the company-scoped reads.
- Send to Slack as well as / instead of a Trimus comment.
Patterns by integration shape¶
| You want… | Use this |
|---|---|
| React in real time to one class of event | SSE stream (Pattern 1) |
| Trigger Trimus from an external event | Routine webhook (Pattern 2) |
| Run a periodic task that reads/writes Trimus | Cron + API (Pattern 3) |
| Give agents a callable tool | Plugin (writing-a-plugin.md) |
| Run heartbeats on your own infrastructure | Worker (building-a-worker.md) |
| Bridge Trimus bidirectionally to a chat platform | SSE outbound + routine inbound (1 + 2) |
| Mirror Trimus state into a warehouse | Cron + API (append-only) |
Where to next¶
- overview.md — re-read the four wiring patterns.
- ../../../API.md — the full endpoint reference.
- ../../../README.md — back to the top.