Skip to content

Commit ea8237d

Browse files
authored
ops: upgrade Paperclip to v2026.512.0
Upgrade Paperclip production config/docs to v2026.512.0, move managed agents to Codex subscription-only auth, add routine revision baseRevisionId support, and switch Staff Engineer PR review triggering to Paperclip's native routine API trigger.
1 parent bb14d0a commit ea8237d

18 files changed

Lines changed: 397 additions & 143 deletions

.github/workflows/staff-engineer-trigger.yml

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,41 @@ jobs:
1515
steps:
1616
- name: Trigger Staff Engineer review
1717
env:
18-
PAPERCLIP_STAFF_ENGINEER_TRIGGER_URL: ${{ secrets.PAPERCLIP_STAFF_ENGINEER_TRIGGER_URL }}
19-
PAPERCLIP_TRIGGER_SECRET: ${{ secrets.PAPERCLIP_TRIGGER_SECRET }}
18+
PAPERCLIP_URL: ${{ secrets.PAPERCLIP_URL }}
19+
PAPERCLIP_API_KEY: ${{ secrets.PAPERCLIP_API_KEY }}
20+
PAPERCLIP_COMPANY_ID: 96ee7b2e-6df2-43c8-bbe3-53e19297308a
21+
PR_NUMBER: ${{ github.event.pull_request.number }}
22+
PR_URL: ${{ github.event.pull_request.html_url }}
23+
PR_SHA: ${{ github.event.pull_request.head.sha }}
2024
run: |
21-
: "${PAPERCLIP_STAFF_ENGINEER_TRIGGER_URL:?missing PAPERCLIP_STAFF_ENGINEER_TRIGGER_URL secret}"
22-
: "${PAPERCLIP_TRIGGER_SECRET:?missing PAPERCLIP_TRIGGER_SECRET secret}"
23-
curl -sSf -X POST \
24-
"$PAPERCLIP_STAFF_ENGINEER_TRIGGER_URL" \
25-
-H "Authorization: Bearer $PAPERCLIP_TRIGGER_SECRET" \
26-
-H "Content-Type: application/json" \
27-
-d '{"pr_number": ${{ github.event.pull_request.number }}, "pr_url": "${{ github.event.pull_request.html_url }}"}'
25+
: "${PAPERCLIP_URL:?missing PAPERCLIP_URL secret}"
26+
: "${PAPERCLIP_API_KEY:?missing PAPERCLIP_API_KEY secret}"
27+
28+
ROUTINES_JSON="$(curl -sSf \
29+
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
30+
"$PAPERCLIP_URL/api/companies/$PAPERCLIP_COMPANY_ID/routines")"
31+
ROUTINE_ID="$(printf '%s' "$ROUTINES_JSON" | jq -r '.[] | select(.title == "[pr:{{pr_number}}] Review") | .id' | head -1)"
32+
: "${ROUTINE_ID:?missing Staff Engineer PR routine}"
33+
API_TRIGGER_ID="$(printf '%s' "$ROUTINES_JSON" | jq -r --arg id "$ROUTINE_ID" '.[] | select(.id == $id) | (.triggers // [])[] | select(.kind == "api" and (.enabled != false)) | .id' | head -1)"
34+
: "${API_TRIGGER_ID:?missing Staff Engineer API trigger}"
35+
36+
jq -n \
37+
--arg trigger "$API_TRIGGER_ID" \
38+
--arg pr "$PR_NUMBER" \
39+
--arg url "$PR_URL" \
40+
--arg idem "pr-$PR_NUMBER-$PR_SHA" \
41+
'{
42+
source: "api",
43+
triggerId: $trigger,
44+
payload: {
45+
pr_number: $pr,
46+
pr_url: $url,
47+
variables: {pr_number: $pr, pr_url: $url}
48+
},
49+
variables: {pr_number: $pr, pr_url: $url},
50+
idempotencyKey: $idem
51+
}' | curl -sSf -X POST \
52+
"$PAPERCLIP_URL/api/routines/$ROUTINE_ID/run" \
53+
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
54+
-H "Content-Type: application/json" \
55+
--data @-

agents/.paperclip.yaml

Lines changed: 28 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
schema: "paperclip/v1"
22

3+
# Codex agents use ChatGPT/Codex subscription OAuth from the persistent
4+
# /paperclip/.codex auth volume. Do not bind OPENAI_API_KEY to codex_local
5+
# agents, and do not set it globally in the Paperclip host env: Codex CLI
6+
# 0.122+ treats that as API-key billing instead of subscription billing.
7+
38
# gstack skills source — Paperclip pulls skills from this repo at the recorded ref.
49
# Bump `ref` only after `agents/deploy.sh --dry-run` shows the new catalog with
510
# zero unknown-desired-skills failures.
@@ -12,11 +17,12 @@ agents:
1217
analyst:
1318
role: "researcher"
1419
adapter:
15-
type: "claude_local"
20+
type: "codex_local"
1621
config:
17-
dangerouslySkipPermissions: true
22+
dangerouslyBypassApprovalsAndSandbox: true
1823
maxTurnsPerRun: 200
19-
model: "claude-sonnet-4-6"
24+
model: "gpt-5.5"
25+
modelReasoningEffort: "high"
2026
runtime:
2127
heartbeat:
2228
enabled: true
@@ -57,21 +63,19 @@ agents:
5763
kind: "plain"
5864
default: "/paperclip/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
5965
requirement: "optional"
60-
OPENAI_API_KEY:
61-
kind: "secret"
62-
requirement: "optional"
6366
ANALYST_DATABASE_URL:
6467
kind: "secret"
6568
requirement: "optional"
6669

6770
comms-manager:
6871
role: "cmo"
6972
adapter:
70-
type: "claude_local"
73+
type: "codex_local"
7174
config:
72-
dangerouslySkipPermissions: true
75+
dangerouslyBypassApprovalsAndSandbox: true
7376
maxTurnsPerRun: 100
74-
model: "claude-sonnet-4-6"
77+
model: "gpt-5.5"
78+
modelReasoningEffort: "medium"
7579
runtime:
7680
heartbeat:
7781
enabled: true
@@ -95,9 +99,6 @@ agents:
9599
FFMEMES_PROD_TELEGRAM_BOT_TOKEN:
96100
kind: "secret"
97101
requirement: "required"
98-
OPENAI_API_KEY:
99-
kind: "secret"
100-
requirement: "optional"
101102

102103
cto:
103104
role: "cto"
@@ -121,9 +122,6 @@ agents:
121122
kind: "plain"
122123
default: "/paperclip/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
123124
requirement: "optional"
124-
OPENAI_API_KEY:
125-
kind: "secret"
126-
requirement: "optional"
127125
ANALYST_DATABASE_URL:
128126
kind: "secret"
129127
requirement: "optional"
@@ -154,11 +152,12 @@ agents:
154152
qa-engineer:
155153
role: "qa"
156154
adapter:
157-
type: "claude_local"
155+
type: "codex_local"
158156
config:
159-
dangerouslySkipPermissions: true
157+
dangerouslyBypassApprovalsAndSandbox: true
160158
maxTurnsPerRun: 200
161-
model: "claude-sonnet-4-6"
159+
model: "gpt-5.5"
160+
modelReasoningEffort: "high"
162161
runtime:
163162
heartbeat:
164163
intervalSec: 21600
@@ -176,18 +175,14 @@ agents:
176175
COOLIFY_BASE_URL:
177176
kind: "secret"
178177
requirement: "required"
179-
COOLIFY_RESOURCE_UUID:
180-
kind: "plain"
181-
default: "k4w804sco4s8kc88kwcw0ow4"
182-
requirement: "optional"
183-
COOLIFY_CONTAINER_NAME:
184-
kind: "plain"
185-
default: "k4w804sco4s8kc88kwcw0ow4-131756368009"
186-
requirement: "optional"
178+
# Coolify resource UUID + container name are intentionally NOT pinned
179+
# as plain defaults here. They are documented in agents/qa-engineer/
180+
# AGENTS.md (the "Key Coolify UUIDs" table) and resolved dynamically
181+
# from the Coolify API when needed. Hand-written UUID defaults rot
182+
# the moment Coolify rebuilds the container.
187183
PREFECT_API_URL:
188-
kind: "plain"
189-
default: "http://65.108.127.32:4200/api"
190-
requirement: "optional"
184+
kind: "secret"
185+
requirement: "required"
191186
ANALYST_DATABASE_URL:
192187
kind: "secret"
193188
requirement: "required"
@@ -227,11 +222,12 @@ agents:
227222
release-engineer:
228223
role: "devops"
229224
adapter:
230-
type: "claude_local"
225+
type: "codex_local"
231226
config:
232-
dangerouslySkipPermissions: true
227+
dangerouslyBypassApprovalsAndSandbox: true
233228
maxTurnsPerRun: 150
234-
model: "claude-sonnet-4-6"
229+
model: "gpt-5.5"
230+
modelReasoningEffort: "medium"
235231
runtime:
236232
heartbeat:
237233
maxConcurrentRuns: 1
@@ -273,9 +269,6 @@ agents:
273269
kind: "plain"
274270
default: "/paperclip/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
275271
requirement: "optional"
276-
OPENAI_API_KEY:
277-
kind: "secret"
278-
requirement: "optional"
279272
ANALYST_DATABASE_URL:
280273
kind: "secret"
281274
requirement: "optional"

agents/_sync_config.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,34 @@ def stale_adapter_keys(adapter_type: str | None) -> set[str]:
335335
return set()
336336

337337

338+
def routine_latest_revision_id(routine: dict) -> str | None:
339+
"""Return the live routine revision id when the Paperclip API exposes one.
340+
341+
v2026.512.0 introduced routine revision history. Older servers simply omit
342+
these fields, so callers can stay compatible by omitting baseRevisionId.
343+
"""
344+
for key in ("latestRevisionId", "latest_revision_id", "currentRevisionId"):
345+
value = routine.get(key)
346+
if isinstance(value, str) and value:
347+
return value
348+
349+
for key in ("latestRevision", "currentRevision", "revision"):
350+
value = routine.get(key)
351+
if isinstance(value, dict):
352+
revision_id = value.get("id")
353+
if isinstance(revision_id, str) and revision_id:
354+
return revision_id
355+
return None
356+
357+
358+
def routine_patch_payload(routine: dict, description: str) -> dict:
359+
payload = {"description": description}
360+
revision_id = routine_latest_revision_id(routine)
361+
if revision_id:
362+
payload["baseRevisionId"] = revision_id
363+
return payload
364+
365+
338366
def sync_routine_descriptions(by_slug: dict[str, dict]) -> tuple[int, int, int]:
339367
specs = load_routine_description_specs()
340368
if not specs:
@@ -373,15 +401,19 @@ def sync_routine_descriptions(by_slug: dict[str, dict]) -> tuple[int, int, int]:
373401
print(f" skip routine {spec['name']} (no description drift)")
374402
skipped += 1
375403
continue
404+
payload = routine_patch_payload(routine, spec["description"])
376405
if DRY:
377-
print(f" WOULD PATCH routine {spec['name']}: description")
406+
change = "description"
407+
if "baseRevisionId" in payload:
408+
change += " with baseRevisionId"
409+
print(f" WOULD PATCH routine {spec['name']}: {change}")
378410
patched += 1
379411
continue
380412
try:
381413
api(
382414
"PATCH",
383415
f"/api/routines/{routine['id']}",
384-
{"description": spec["description"]},
416+
payload,
385417
)
386418
except Exception as e:
387419
print(

agents/analyst/AGENTS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ delegated subtasks instead of comment-only handoffs.
3535

3636
Every issue you create must start with a stable bracket slug. Use
3737
`[report:YYYY-MM-DD]` for scheduled reports and update/comment on an existing
38-
open issue with that slug instead of creating duplicates.
38+
open issue with that slug instead of creating duplicates. Use native Paperclip
39+
company search / issue search before creating a recurrent slug.
3940

4041
Only the CEO may open strategic issues. As Analyst, create only execution
4142
tickets from your explicit workflow; put strategic findings in your report for

agents/ceo/AGENTS.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ Use the native `paperclip` skill for wake context, task selection, checkout,
4646
structured confirmations, blockers/subtasks, documents/attachments, concise
4747
comments, and task completion.
4848

49+
Use Paperclip planning work mode for strategic, experiment, architecture, and
50+
proposal issues. Keep standard mode for execution tickets delegated to CTO,
51+
Analyst, or Comms.
52+
4953
For blocked work, set status `blocked` with a clear comment and use
5054
`blockedByIssueIds` when another issue must finish first. Use child issues for
5155
delegated subtasks instead of comment-only handoffs.
@@ -58,8 +62,8 @@ across recurrences:
5862
`[post:YYYY-MM-DD-slug]`, `[maintenance:<slug>]`, `[postmortem:<slug>]`,
5963
`[strategy:weekly-outcomes-YYYY-MM-DD]`
6064

61-
Search/update an existing open issue with the same slug before creating another
62-
one.
65+
Use native company search / issue search to find an existing issue with the same
66+
slug before creating another one.
6367

6468
Only the CEO may open strategic issues. Other agents may open execution issues
6569
from their explicit workflows and should route strategic ideas through you.

agents/comms-manager/AGENTS.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ For blocked work, set status `blocked` with a clear comment and use
3939
Every post draft issue must use `[post:YYYY-MM-DD-slug]` as the stable title
4040
prefix.
4141

42-
Search/update an existing open draft with the same slug before creating another
43-
one.
42+
Use native Paperclip company search / issue search and update an existing open
43+
draft with the same slug before creating another one.
4444

4545
You may create only execution tickets from the comms workflow. Strategic or
4646
planning tickets belong to CEO.
@@ -161,10 +161,9 @@ For exact data, use `src/comms/visuals.py` primitives ONLY. Do not write raw mat
161161
- Pie charts, 3D, dual-axis → banned
162162

163163
These primitives return PNG bytes. Pass them directly as `photo_bytes=png` to
164-
`publish_editorial_post`. For illustrative/editorial art, use
165-
`src.comms.image_generation.generate_editorial_image()` with a precise prompt
166-
built by `build_editorial_image_prompt(...)`, then pass
167-
`photo_bytes=image.image_bytes`.
164+
`publish_editorial_post`. Under Codex subscription-only runtime, do not use
165+
`OPENAI_API_KEY` or GPT image generation inside this agent. If editorial art is
166+
required, create a CEO/ops task for a separate non-Codex image pipeline.
168167

169168
See `docs/comms/brand-guide.md` for the full decision tree and constraints.
170169

@@ -278,16 +277,16 @@ See full brand guide: `docs/comms/brand-guide.md`
278277
4. **Diagrams** — for engineering posts (simple, clean, brand colors)
279278
5. **Stat cards** — for daily pulse (big number + context)
280279

281-
When generating charts/images, verify the result looks good by feeding it back through the browse skill.
280+
When creating charts or local visuals, verify the result looks good by feeding
281+
it back through the browse skill.
282282
Never send editorial visuals to the moderator chat just to get a Telegram `file_id`.
283283

284284
### Image Review Before Posting (MANDATORY)
285285

286286
Before attaching ANY image to a channel post, you MUST:
287287

288288
1. **Visually inspect** the image. For Telegram `file_id` memes, download via
289-
the Telegram Bot API first; for local/generated images, inspect the local
290-
PNG directly.
289+
the Telegram Bot API first; for local images, inspect the local PNG directly.
291290
```bash
292291
# Get file path
293292
FILE_PATH=$(curl -s "https://api.telegram.org/bot${FFMEMES_PROD_TELEGRAM_BOT_TOKEN}/getFile?file_id=<file_id>" | python3 -c "import json,sys; print(json.load(sys.stdin)['result']['file_path'])")

agents/cto/AGENTS.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ Use the native `paperclip` skill for wake context, task selection, checkout,
2424
structured confirmations, blockers/subtasks, documents/attachments, concise
2525
comments, and task completion.
2626

27+
When CEO asks for architecture or implementation planning rather than immediate
28+
execution, keep that work in Paperclip planning mode until the plan is accepted.
29+
2730
For blocked work, set status `blocked` with a clear comment and use
2831
`blockedByIssueIds` when another issue must finish first. Use child issues for
2932
delegated subtasks instead of comment-only handoffs.
@@ -34,8 +37,8 @@ Every issue you create must start with a stable bracket slug, reused across
3437
recurrences: `[pr:NNN]`, `[incident:<slug>]`, `[deploy:<branch-or-pr>]`,
3538
`[maintenance:<slug>]`, `[postmortem:<slug>]`.
3639

37-
Search/update an existing open issue with the same slug before creating another
38-
one; this collapses repeated incidents onto one tracking issue.
40+
Use native Paperclip company search / issue search for the same slug before
41+
creating another one; this collapses repeated incidents onto one tracking issue.
3942

4043
You may create only execution tickets from your implementation workflow.
4144
Strategic/planning tickets belong to CEO.

agents/qa-engineer/AGENTS.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,16 @@ recurrences:
4646
- `[incident:<slug>]` — production bugs (e.g. `[incident:db-pool]`, `[incident:describe-memes-timeout]`, `[incident:webhook-502]`)
4747
- `[deploy:<branch-or-pr>]`, `[report:YYYY-MM-DD]`, `[maintenance:<slug>]`, `[postmortem:<slug>]`
4848

49-
Search/update an existing open issue with the same slug before creating another
50-
one; add new evidence as a comment instead of opening duplicates.
49+
Use native Paperclip company search / issue search and update an existing open
50+
issue with the same slug before creating another one; add new evidence as a
51+
comment instead of opening duplicates.
5152

5253
As QA, create only execution tickets from scan workflows. Planning and strategic
5354
tickets belong to CEO.
5455

56+
For delayed verification, post-deploy waits, or "retry after logs settle" work,
57+
use native issue monitors / retry-now rather than comment-only due timestamps.
58+
5559
## Every Scheduled Log Scan
5660

5761
### 1. Scan All Log Sources
@@ -169,6 +173,9 @@ outcomes. There are two distinct layers and you should not duplicate them:
169173
zombie-run, and no-comment classification belong here. Read these from the
170174
Paperclip dashboard / native routine tooling — do NOT reimplement them in
171175
the FFmemes audit script.
176+
- **Paperclip v2026.512 issue monitor signals** — delayed checks and retry-now
177+
belong to the native monitor surface. Use it before inventing a custom
178+
follow-up comment format.
172179
- **FFmemes outcome-contract checks**, run via
173180
`scripts/paperclip_routine_audit.py`. This is narrow and business-specific:
174181
channel post publication markers, update-check content (changelog, version,

agents/release-engineer/AGENTS.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ delegated subtasks instead of comment-only handoffs.
3232
Every issue you create must start with a stable bracket slug. For release work
3333
this is usually `[pr:NNN]`, `[deploy:<branch-or-pr>]`, or `[qa:<pr-number>]`.
3434

35-
Search/update an existing open issue with the same slug before creating another
36-
one.
35+
Use native Paperclip company search / issue search for the same slug before
36+
creating another one.
3737

3838
You may create only execution tickets from your release workflow. Strategic or
3939
planning tickets belong to CEO.
@@ -54,6 +54,9 @@ You do **NOT** merge PRs. Staff Engineer owns the review → approve → squash-
5454
2. **Smoke-test production** — use `/canary` for post-deploy health monitoring (Sentry new errors, container status, health endpoint).
5555
3. **Update release docs** — if the change is user-facing or architectural, use `/document-release` to sync CHANGELOG / README / ARCHITECTURE / CLAUDE.md.
5656
4. **Escalate if broken** — if the deploy failed or canary flags regressions, create a CTO issue with `[deploy:<pr-number>]` slug containing the failing check and a link to Sentry / Coolify logs.
57+
5. **Schedule delayed checks natively** — if logs, metrics, or deploy state need
58+
time to settle, use Paperclip issue monitors / retry-now instead of leaving
59+
a comment-only due timestamp.
5760

5861
## Merge Policy (reminder, not your job)
5962

0 commit comments

Comments
 (0)