Run agents on a Prized box with OpenRouter models
Run task agents on a Prized box with cheap OpenRouter models: webhooks in, two-stage draft and review, model keys kept off the box, results.
Last verified
In this guide
Assign a card to an agent Profile and a small always-on box picks it up, does the research with the same tools your other agents have, and hands the card back to you for review. The models run on OpenRouter, so no subscription is used and each card costs cents.
Allow about an hour. You need a Prized account, a Takibi account with a task board and an agent Profile, an OpenRouter key, and a Tailscale account. Add a Composio account if your agents need the web. The box costs about $10/month on a Prized plan; models are billed per token by OpenRouter.
What you are building
Takibi sends a signed webhook when a card is assigned to the agent's Profile. A Prized box receives it through Tailscale Funnel, claims the card, and runs the pi agent harness twice:
- Draft: a fast model reads the card and its artifacts, researches with the
takibiCLI and the Composio CLI (Firecrawl for web pages, Drive and the rest of your connections), and writes a draft on the box. - Review: a deeper model checks the draft's claims, applies your collection's own docs, attaches one final artifact, and finishes the card as the
takibi-useskill says: a handoff to the Profile the card names, or review with a note.
Every run ends in review, the one state that stays in your inbox after the run ends. If the agent leaves the card anywhere else, the worker moves it there with a note that starts Needs you:.
Create the box
Prized dashboard Create the smallest box, Nano (2 vCPU, 2 GB, 40 GB disk). On ours, a run's agent process sat around 100 MB and spent most of its time waiting on the model and the web. Copy the box id from its page; the secrets step needs it.
Keep the box awake. Inbound traffic never wakes a paused Prized box, so a sleeping box only sees Takibi's retries. Turn auto-pause off on the box page now, or with PATCH /api/v1/boxes/<id> and {"autoPauseMin": null}, before installing anything. Don't use quiet hours either: per Prized's docs, a box woken inside quiet hours pauses again after 15 minutes. Price on 2026-10-26: $0.014/h awake, $0.004/h asleep, and the $10/month plan covers one Nano around the clock, so sleep saves almost nothing on a plan. See box states and auto-pause and billing.
Open the terminal with its plain URL, /dashboard/boxes/<id>/terminal, or with prized ssh <box>. A terminal link ending in ?agent=claude (the onboarding tour's Start in your browser opens one) starts Claude Code in its own session, which holds about 150–220 MB of the 2 GB. The Open terminal button reopens the way in you used last; pick Terminal from its chevron once. If a Claude Code session is already running and you don't need it, tmux kill-session -t claude closes it.
Install the CLIs and skills
Box terminal Open the box's terminal in the dashboard, or prized ssh <box> from your laptop. Give this prompt to an agent there (Claude Code ships on the box), or follow it yourself in Bash. It installs Node, the takibi CLI and its skill, the pi harness, and optionally the Composio CLI. pi reads skills from ~/.agents/skills, which is where both skills must end up.
BOX / Prized box terminal (dashboard terminal or prized ssh <box>). Use Bash as the box's login user, not root.
Goal: Node 22.21+, the takibi CLI + skill, the pi agent harness, and (if this agent needs the web) the Composio CLI, all for this user.
1. Node: curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt-get install -y nodejs. node -v must report v22.21.0 or newer (older 22.x ignores NODE_USE_ENV_PROXY, which the worker needs). Stop if it doesn't.
2. CLIs: sudo npm i -g takibibase @earendil-works/pi-coding-agent. takibi --version and pi --version must both print a version.
3. Stop and let the human enter the Takibi key (next step of the guide). Then: mkdir -p ~/.agents/skills && takibi skill --install --force. pi reads skills from ~/.agents/skills; confirm ~/.agents/skills/takibi-use/SKILL.md exists.
4. Gate: takibi projects must list the collection the agent will work on. An empty or unexpected list means the Profile's grants are wrong; stop.
5. Web access (skip if this agent won't use the web): the human runs curl -fsSL https://composio.dev/install | sh, then ~/.composio/composio login. The installer adds ~/.local/bin/composio and the composio-cli skill under ~/.agents/skills. Confirm with composio whoami. Never print or paste any key.
6. Report the versions, the takibi projects result, and the ~/.agents/skills listing. Do not start any service yet.Enter the Takibi key
Takibi app Open Settings → Profiles and create a Profile for this box, for example Prized. Task boards need a whole-collection grant, so grant it the whole collection for each board it will work on. Leave Create tasks off. Create its key.
The key is shown once and goes to two places, so keep it on screen until both are done:
- Box terminal Run the block below yourself and type the key at the hidden prompt. The CLI checks the key's
<publicId>.<secret>shape locally, so this file holds the real key, mode600. - Prized dashboard Save the same key as a Prized Secret named
TAKIBI_KEY(next section), so the worker's own API calls go through the broker.
# TERMINAL / human only. Open Bash in the Prized box terminal.
# Type the key at the hidden prompt, never as a shell command or in chat.
# Refuse placeholders such as <PASTE-KEY-HERE>; preserve any existing key.
(
set +x
umask 077
mkdir -p "$HOME/.takibi" || exit 1
chmod 700 "$HOME/.takibi" || exit 1
if [ -e "$HOME/.takibi/key" ] || [ -L "$HOME/.takibi/key" ]; then
printf '%s\n' 'Key already exists. Review its grants; do not overwrite it.'
exit 1
fi
IFS= read -r -s -p 'Takibi key (hidden): ' takibi_key
printf '\n'
case "$takibi_key" in
''|*'<'*|*'>'*) unset takibi_key; printf '%s\n' 'Empty key or placeholder refused.'; exit 1 ;;
esac
set -C
printf '%s\n' "$takibi_key" > "$HOME/.takibi/key" || exit 1
unset takibi_key
chmod 600 "$HOME/.takibi/key"
)Keep the model key off the box
Prized dashboard Add the OpenRouter key as a Prized Secret named OPENROUTER_API_KEY, and the Takibi key as TAKIBI_KEY. Grant each one to this box only: an ungranted secret is open to every box in your Prized workspace. The box then sees only a dpsec_… placeholder. Prized's local broker swaps in the real value on HTTPS requests to the hosts you allow, so a placeholder is useless anywhere else.
That covers these two keys, not everything on the box. The CLI key file, the Composio login, and the webhook signing secret and receiver auth in the worker's env file are real values, readable by anything running as this user, agents included. Treat the box's env files and transcripts as sensitive.
Laptop Set each secret's allowed host and mount it on the box. We did that through the Prized API; give this prompt to a laptop agent that can make HTTP requests, with a Prized API key from your local secret store. Note the two placeholders it reports.
LOCAL / laptop, with a Prized API key read from your local secret store (never chat). Inputs: the box id from the dashboard, and the ids of two Prized Secrets the human already created in the dashboard: OPENROUTER_API_KEY and TAKIBI_KEY (the agent Profile's Takibi key).
1. Each secret must be granted to this box only (an ungranted secret is open to every box in the workspace) and allow only its own host. Check with GET https://api.prized.dev/api/v1/secrets, then set hosts where missing:
PATCH https://api.prized.dev/api/v1/secrets/<openrouterSecretId> {"hosts":["openrouter.ai"]}
PATCH https://api.prized.dev/api/v1/secrets/<takibiSecretId> {"hosts":["app.takibibase.com"]}
If a secret has no box grant, stop and tell the human to grant it to this box in the dashboard.
2. Mount both on the box (envName is required):
POST https://api.prized.dev/api/v1/boxes/<boxId>/secrets {"secretId":"<openrouterSecretId>","envName":"OPENROUTER_API_KEY"}
POST https://api.prized.dev/api/v1/boxes/<boxId>/secrets {"secretId":"<takibiSecretId>","envName":"TAKIBI_KEY"}
3. Report each status code and the two dpsec_… placeholders the mounts return (placeholders are not secrets; the real values never leave Prized).
Auth header on every call: Authorization: Bearer <Prized API key>. Never print the key.Two things the broker needs that systemd doesn't give you. Prized wires the proxy into login shells through /etc/profile.d, which services never read. And Node's fetch ignores HTTPS_PROXY unless NODE_USE_ENV_PROXY=1 is set, which needs Node 22.21 or newer. The worker's service file below sets both, plus the broker's CA certificate. To check from the box, call OpenRouter's /api/v1/key with the placeholder: 200 through the proxy, 401 without it.
Let Takibi reach the box
Prized boxes take no connections from the internet. Their shared port links either need a Prized sign-in, which Takibi can't do, or expire within 30 days. Tailscale Funnel works instead: the box connects out, and you get a stable public HTTPS URL with no redirects, which Takibi's webhook sender requires.
Tailscale admin Open Access controls and merge this into your policy file. The grant replaces the default allow-all rule ("src": ["*"]); a narrower rule added next to a broad one revokes nothing. Then turn on MagicDNS and HTTPS certificates under DNS, and generate a one-time, non-ephemeral auth key tagged tag:agent under Settings → Keys, expiring in a day. Tagged devices never hit node key expiry.
{
"tagOwners": {
"tag:agent": ["autogroup:admin"]
},
"grants": [
// Replaces the default allow-all ("src": ["*"]). Tagged devices are not
// members, so tag:agent can't start connections to anything.
// autogroup:member is every member, invited users included; use your own
// login instead if you share the tailnet.
{"src": ["autogroup:member"], "dst": ["*"], "ip": ["*"]}
],
"ssh": [
{"action": "check", "src": ["autogroup:member"], "dst": ["autogroup:self"], "users": ["autogroup:nonroot", "root"]},
{"action": "check", "src": ["autogroup:member"], "dst": ["tag:agent"], "users": ["autogroup:nonroot"]}
],
"nodeAttrs": [
// Only agent boxes may publish a port with Funnel.
{"target": ["tag:agent"], "attr": ["funnel"]}
]
}Box terminal Join the box yourself with the block below; the auth key never touches history or an agent. Note the https://<box>.<tailnet>.ts.net URL that tailscale funnel status prints. Until the worker runs, requests to it fail; afterwards an unsigned POST gets 401.
# TERMINAL / human only, in the Prized box terminal, after installing Tailscale
# (curl -fsSL https://tailscale.com/install.sh | sh). Paste the auth key at the hidden
# prompt; it goes to a temporary 600 file, never into history or an agent.
(
set +x
umask 077
IFS= read -r -s -p 'Tailscale auth key (hidden): ' ts_key
printf '\n'
case "$ts_key" in tskey-*) ;; *) unset ts_key; printf '%s\n' 'Not a tskey- auth key.'; exit 1 ;; esac
keyfile=$(mktemp) || exit 1
printf '%s' "$ts_key" > "$keyfile"
unset ts_key
sudo tailscale up --auth-key="file:$keyfile" --advertise-tags=tag:agent --hostname="$(hostname)" --ssh
status=$?
rm -f "$keyfile"
exit $status
)
sudo tailscale funnel --bg 8787
sudo tailscale funnel statusBuild the worker
Box terminal The worker is one dependency-free Node file run by a systemd user service. Create ~/agent/agent.env from the template below (the webhook secret comes in the next section, so leave it blank for now), save the unit file, then hand the spec to an agent on the box to write ~/agent/agent.mjs. The spec carries the signature rules inline, so the agent doesn't need any other file.
The spec leaves how to finish the card to the takibi-use skill. When the card says assign to @name, the skill hands it off; otherwise it moves it to review. When our wrapper prompt dictated the finishing command, runs followed it, and cards stayed with the agent instead of reaching us.
# ~/agent/agent.env on the box, mode 600 (chmod 600 ~/agent/agent.env)
# Real values, checked on the box: the broker can't hold these.
WEBHOOK_SECRET=<signing secret, shown once when you create the Takibi endpoint>
RECEIVER_AUTH=Bearer <output of: openssl rand -hex 32>
# Prized placeholders, not the real keys:
TAKIBI_KEY=<dpsec_ placeholder for the Takibi secret>
OPENROUTER_API_KEY=<dpsec_ placeholder for the OpenRouter secret>
TAKIBI_BASE=https://app.takibibase.com
MODEL=openai/gpt-6-luna-pro
REVIEW_MODEL=xiaomi/mimo-v2.6-pro
RUN_TIMEOUT_MIN=20
REVIEW_TIMEOUT_MIN=12# ~/.config/systemd/user/takibi-agent.service
[Unit]
Description=Takibi webhook worker
After=network-online.target
[Service]
EnvironmentFile=%h/agent/agent.env
# Composio installs to ~/.local/bin, which systemd's default PATH lacks.
Environment=PATH=%h/.local/bin:/usr/local/bin:/usr/bin:/bin
# Prized's broker: services don't read /etc/profile.d, so set the proxy here.
Environment=HTTPS_PROXY=http://127.0.0.1:7379
Environment=NO_PROXY=localhost,127.0.0.1,::1,169.254.169.254
Environment=NODE_USE_ENV_PROXY=1
Environment=NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/prized-secretsd.crt
Environment=NODE_NO_WARNINGS=1
ExecStart=/usr/bin/node %h/agent/agent.mjs
Restart=always
RestartSec=5
[Install]
WantedBy=default.target
# then, once agent.env and agent.mjs exist:
# systemctl --user daemon-reload
# sudo loginctl enable-linger "$USER" # loginctl show-user "$USER" -p Linger must say yes
# systemctl --user enable --now takibi-agent.service
# journalctl --user -u takibi-agent -fBOX / write ~/agent/agent.mjs: one dependency-free Node file (node:http, node:crypto, node:child_process, node:fs) that the systemd unit runs. It reads its settings from the environment (agent.env).
Receive (HTTP on 127.0.0.1:8787, POST only):
1. Verify Standard Webhooks signatures: HMAC-SHA256 with WEBHOOK_SECRET over `${webhook-id}.${webhook-timestamp}.${rawBody}`, base64; the webhook-signature header holds one or more space-separated "v1,<sig>" values; compare in constant time; refuse timestamps more than 5 minutes off. If RECEIVER_AUTH is set, the Authorization header must equal it (constant time). Refuse with 401 otherwise.
2. Parse the JSON body; require schemaVersion 1 and deliveryId equal to webhook-id. Dedupe on eventId (keep the seen ids on disk).
3. webhook.ping: answer 200 and log "ping ok". Task events: act only on task.assignment_changed with cause assigned or reassigned (in collapsed deliveries, look through the changes array); for those, persist {taskId: resource.id, projectId} to a pending queue on disk. Answer 200 for everything that verified, then work the queue one card at a time.
Work one card:
4. GET ${TAKIBI_BASE}/v1/tasks/<id>?projectId=<projectId> with "Authorization: Bearer ${TAKIBI_KEY}"; skip if archived, blocked or not in todo. POST …/claim: 200 means it's yours, 409 means another Profile holds it. PATCH status in_progress.
5. Drafter: MODEL, unless the card body has a line "model: <openrouter id>" (allow backticks or bold around it; ignore anything that isn't an id like "vendor/model"). Make a per-card work dir and run pi -p --model openrouter/<drafter> --session-dir <new dir> "<draft prompt>" with RUN_TIMEOUT_MIN as a hard timeout. Draft prompt: the card id and collection id; the run is unattended and ends when it replies; use the takibi-use skill and the takibi CLI, and the composio CLI for the web; write the whole deliverable to draft.md in the work dir with a closing "Notes for the reviewer" section (sources, what couldn't be verified); do not change the card in Takibi.
6. Reviewer: run pi -p --model openrouter/${REVIEW_MODEL} --session-dir <another new dir> "<review prompt>" with REVIEW_TIMEOUT_MIN. Review prompt: this is a review pass, not a redo; verify the draft's key claims and the points in its reviewer notes; check the collection's own docs in Takibi and apply them; fix errors and fill only clear gaps; don't re-scrape what the draft covered or chase what it marks unverifiable; attach ONE final markdown artifact; then finish the card as the takibi-use skill says. Do not tell it which command to finish with: the skill hands the card off when the card names someone, and a dictated command overrides that.
7. If the reviewer times out, run pi once more with the same --session-dir plus --continue and the prompt "Time's up. Stop researching, attach your final deliverable with what you have (mark anything unverified), finish the card as the takibi-use skill says." (3-minute timeout).
8. Close every pi child's stdin right after starting it: pi -p waits on piped stdin otherwise and the run hangs.
9. Afterwards GET the card. If it is not in review, attach draft.md (if any) as a markdown artifact titled "Draft (not reviewed)", then PATCH status review with reviewNote "Needs you: <what happened>". Never mark it blocked: blocked review cards drop out of the inbox. On any error, do the same.
10. Log, per stage: model, seconds, turns and cost (sum message.usage.cost.total from the .jsonl in that stage's session dir).Point a Takibi webhook at it
Takibi app Create a webhook endpoint with the Funnel URL as its wake URL and choose the boards it covers. Under Triggers, keep only Assignment, set Only cards held by to the agent's Profile, set Owned by to the same Profile with Skip the owner's own writes on, so the agent's own moves never wake it again, and turn on Collapse one action into a single delivery. See delivery filters.
Copy the signing secret, which is shown once, and set a receiver auth value: Bearer plus the output of openssl rand -hex 32. Put both into ~/agent/agent.env, not into Prized Secrets: the worker checks them on the box, where the broker can't help. Start the service with the commands at the end of the unit file, then choose Test webhook; the log should show ping ok.
New endpoints start with deliveries off. Once the test passes, switch Enabled on. Until then, assigning a card wakes nothing.
Run your first card
Takibi app Create a card on a covered board and end its body with assign to @<your Profile name>, using the exact name of the Profile marked as yours, so the agent knows who gets it back. Assign the card to the agent's Profile. Deliveries can sit in pending for a short while before they go out. The worker claims the card, the drafter and reviewer run, and the card lands in your inbox with one artifact.
To try another model on one card, add a line such as model: z-ai/glm-5.3 to its body; the drafter uses it instead of the default. It must be an exact OpenRouter id. Refresh pi's catalog with pi update --models when a model is new, or its fuzzy matching may pick a retired variant.
Which models we picked, and why
We gave the same fresh SEO research card to each model on 2026-10-26, one agent run per model except DeepSeek, which we ran several times. A handful of runs is not a benchmark. Treat this as a starting point and rerun it on your own work.
| Model (OpenRouter id) | Time | Cost | What happened |
|---|---|---|---|
openai/gpt-6-luna-pro | 3.5 min | $0.03 | Finished, handed off correctly, careful about claims. Our drafter. |
xiaomi/mimo-v2.6-pro | 7 min | $0.03 | Finished, handed off correctly, used the collection's docs most. Our reviewer. |
z-ai/glm-5.3 | 5 min | $0.10 | Strong, at three times the price. |
deepseek/deepseek-v4-pro | 1–5 min | $0.03–0.09 | Time and cost moved a lot between runs; one run contradicted a term written on the card. |
inclusionai/ling-3.1-flash | 20 min | $0.03 | Timed out: kept researching and never wrote up. |
minimax/minimax-m3.1-flash | 20 min | $0.08 | Timed out after 69 commands. |
Our first two-stage run on that card, before the reviewer had a budget, took about 20 minutes and $0.13: the drafter 2 minutes, the reviewer 17. It gave the most usable result of the day, with exact placement, the collection's own rules cited, and a list of what it couldn't check. The 12-minute review budget and the wrap-up turn exist so a review of a mostly finished draft doesn't run that long. We shortlisted from OpenRouter's tau2-bench airline results, which measure agentic tool use rather than SEO.
When something breaks
Match the symptom:
- Run hangs, 0 turns, $0
Box terminal
pi -pis waiting on stdin. Close the child's stdin when the worker starts it.- Test passes, cards never arrive
Takibi app The endpoint's Enabled switch is still off, or the card is held by a different Profile than the trigger filter names.
- OpenRouter 401 from the box
Box terminal The secret isn't mounted or granted to this box, its allowed host isn't
openrouter.ai, the service lacks the proxy variables, or Node is older than 22.21.- 404 "free launch period ended"
Box terminal pi's offline catalog is stale and matched a retired
:freevariant. Runpi update --modelswith the proxy variables set.- Card ran on the default model
Takibi app The
model:line didn't parse. Keep it on one line asmodel: <id>; a doubledmodel: model:falls back.- claim 400 or 409
Takibi app 400: the worker uses an owner key, not the agent Profile's. 409: another Profile holds the card.
- not eligible (status …)
Takibi app The worker only starts cards in To do. Move it back, unassign, and assign it to the agent again.
- Delivery 401, "rejected delivery"
Box terminal The signing secret or the receiver auth value in the env file doesn't match the endpoint. The auth value must include
Bearer.- Review stage timed out
Takibi app The card arrives with the unreviewed draft attached and a
Needs you:note. The draft survives; whatever the review did before the wrap-up turn is lost. Rerun once the card has what was missing.- Cheap flash models time out
Box terminal On our research card, two flash models never decided they were done. We didn't test them on narrow lookups, so don't assume they work there either.
- 422 SECRET_BLOCKED
Box terminal The agent tried to write a key into a card, artifact or note. Takibi refused it, as it should. Find out why the agent had it.
Keep it running
Box terminal A daily systemd user timer running sudo -n npm i -g takibibase@latest keeps the CLI current. pi reads the skill from ~/.agents/skills, so follow each upgrade with takibi skill --install --force; new skill rules then apply on the next card without restarting the worker. Refresh pi's model catalog with pi update --models now and then.
The box has a shell, the agent Profile's key and your live Composio connections. Treat it like a teammate's laptop: grant the Profile only the collections it needs, keep it tagged and walled off from your tailnet, and rotate any key that has been pasted anywhere it shouldn't be.
Ready to set this up?Create your workspace and connect your first agent — this guide walks you through each step.
Create your workspace