[ OPERATIONS · OPERATOR UTILITY ]
/improve-queue
Run the improvement-queue auto-implementer.
ACME Agency, <id> and you@example.com mark values that are per-agency — your install fills them with YOUR clients and accounts. If a section references a helper script you don't have yet, it ships with that workflow's install./improve-queue — daily auto-improver
Closes the loop on the improvement queue. Colleagues' sandboxed Claude sessions write proposals to ~/scratch/improvements/; the root aggregator (17:00 Sarajevo) bundles them to a Drive doc + #your-channel post AND drops a machine-readable /opt/improvements/pending/<date>.json. This skill is the step that used to be manual ("Faris pastes the doc and says apply this") — it reads those bundles and acts on them.
When to trigger
- Automatic: your server cron under user
faris, ~17:30 Sarajevo (after the aggregator). Seeshared/your server_setup/install_improver_cron.sh. - Manual: the user says "run the improvement queue", "process the improvements", "apply queued improvements", "what's in the improvement queue", or invokes
/improve-queue.
How to run it
This skill is a thin wrapper around the orchestrator. When invoked:
- Run the orchestrator from the repo root:
- Default (safe):
node shared/improvement_queue/improver.mjs→ shadow mode (applies each safe proposal, runs the gate, shows the diff, then reverts — nothing is committed or pushed). - Live:
node shared/improvement_queue/improver.mjs --live→ commits one revertable commit per proposal (fingerprint in the message) and pushes tomain; the deploy cron pulls into/optwithin 60s.
- Relay the digest it prints (auto-applied / would-apply / held-for-ACME Agencyw / already-live / needs-attention). The orchestrator also posts the digest to
#your-channel.
Do not hand-edit the queue state. The orchestrator owns git + Slack + the gate.
Triage boundary (who decides what)
Triage is deterministic and defaults to MANUAL on any doubt (triage() in improver.mjs):
| Proposal | Decision |
|---|---|
Landing-page edit (any category — touches websites/ or describes a live-lander copy/CSS/section/sub-page change) | bounced → "route via /landing-edit" |
doc-gap (CLAUDE.md / SKILL.md / *.md text) | auto |
client-config (clients.json / config JSON backfill) | auto |
bug, severity low/medium, scoped to a single skill:, no infra/spend keyword | auto |
bug that is high-severity, has no skill, or mentions infra/secret/spend | manual |
new-skill / feature-request / process | manual |
Infra/spend keywords that force a bug to manual: .env, secret/token/credential, oauth/password, bridge, clickup_executor, sandbox_guard, preflight, aggregator, ecosystem, meta_lead_ads / meta ad / ad set / campaign / pixel / spend, google ads, billing/payment, webhook, git push.
Landing edits are bounced before triage (isLandingEditProposal() in improver.mjs): a proposal that touches websites/ or describes a change to a deployed lander never enters the auto-apply path — it's held with a "route via /landing-edit" reason and no worker is spawned. Landing edits must go through /landing-edit (auto-deploy, no approval); a sandbox session that writes one as an improvement proposal has bypassed that path. websites/ is also a separate repo and can't be committed from the main repo regardless. Incident: 2026-06-19.
Safety gate (the real guardrail — enforced in code, not by the LLM)
For every auto item the orchestrator:
- requires a clean tracked working tree on
mainbefore starting (else it skips the whole run and Slacks why); - spawns an apply-one worker (see contract below) to make the edit;
- re-derives the actually-changed files from git (ignores the worker's self-report) and reverts + reclassifies to manual if any file matches the PROTECTED denylist —
.env,.git,.claude/hooks, the bridge, the ClickUp executor, anything undershared/improvement_queueorshared/your server_setup,run_claude_subprocess.mjs,ecosystem.config.*,meta_lead_ads.mjs/meta_web_leads.mjs/google_ads_optimize.mjs, anything underwebsites/(separate repo + gitignored here — landing edits go through/landing-edit, never a proposal), or any secret/credential/token path; - runs
node --checkon every changed JS file andJSON.parseon every changed JSON file — any failure reverts the item; - only then (live mode) commits that one proposal and pushes.
Each proposal starts from a clean tree and ends clean (committed-in-live, or reverted-in-shadow/blocked/failed/commit-failed), so items never bleed into each other. A commit/push that fails reverts its own changes too — otherwise the dirty files leaked into the next proposal's git add and cascaded the failure (incident 2026-06-19).
Apply-one worker contract
The orchestrator spawns one claude -p worker per auto proposal (sandbox:false, Max auth). Its rules (also embedded in the spawn prompt):
- Check "already applied" first. Sandbox clones lag, so many proposals are already live on
main. If so → make no edits, reportalready-done. - Make the minimal change; no unrelated refactors.
- May edit: docs (
*.md, CLAUDE.md, SKILL.md), client config JSON, and the single client/skill script the proposal targets. - Must NOT edit any PROTECTED path (see gate) or run git / Slack / ad-creating skills. If the proposal needs a protected path → report
needs-human. - Run
node --checkon changed JS; follow the bug-fix protocol (update the matching CLAUDE.md/SKILL.md + incident line where called for). - End with one line:
###RESULT### {"status":"applied|already-done|needs-human|failed","summary":"…","files":[…],"reason":"…"}
Architecture
colleague sandbox sessions → ~/scratch/improvements/*.md
│ (root cron 17:00)
▼
aggregator.mjs → Drive doc + #your-channel post + /opt/improvements/pending/<date>.json (0644)
│ (faris cron 17:30)
▼
improver.mjs (this skill) → triage → apply-one worker → GATE → commit+push (live) / diff+revert (shadow) → digest
│ (deploy cron, 60s)
▼
/opt/agency-os updated → colleagues' clones sync
State: ~/.improver/improver-state.jsonl (one line per processed fingerprint, in faris's home so the cron needs no root). Pending bundles are READ from /opt/improvements/pending/ (world-readable, written by the root aggregator). Kill switch: touch ~/.improver/improver.disabled.
Deploy (as faris, no sudo): the improver needs no root — /opt/agency-os auto-pulls every 60s, and faris's own clone + crontab are user-writable.
cd ~/agency-os && git pull
bash shared/your server_setup/install_improver_cron.sh # shadow (default)
# …after ~a week of clean digests:
bash shared/your server_setup/install_improver_cron.sh --live
Tuning
- Loosen/tighten the auto class: edit
MANUAL_SIGNALSandtriage()inimprover.mjs. - Add a never-auto path: append to
PROTECTEDinimprover.mjs. - Flip from shadow to live: change the cron in
install_improver_cron.sh(add--live) and re-run it on your server, or setIMPROVER_MODE=live.
Rollout
Ships in shadow mode. Watch ~a week of digests (it shows exactly what it would auto-apply and what it holds), confirm the classifier, then flip to --live.