Coding

Agent Queue To Reviewed Pr

Try it

Turn a ticket queue into reviewed draft PRs with an agent that never merges. Use when wiring an agent to a ticket board or issue tracker. Trigger on "auto PR...

What it does

**The agent proposes, a human validates. Every link in the loop must have a consumer — an unconsumed state is a ticket stuck forever.**

The skill document

Agent Queue → Reviewed PR

The agent proposes, a human validates. Every link in the loop must have a consumer — an unconsumed state is a ticket stuck forever.

Access, data and network — read before wiring anything

Default is inert: no variable set → no outbound call. Every network destination is opt-in and listed here — there are no others.

ThingWhat it isDefault
${AGENT_API_TOKEN}Bearer for all four endpoints below. Keep it in a file outside git, never in the workspace, never in a notification.Unset → this skill is inert, no call leaves the machine
${QUEUE_URL}Outbound read to your work source. Ticket ids, titles and briefs cross the network.Unset → inert
${ACK_URL}Outbound write: ticket id + PR URL.Unset → inert
${GROUNDING_ACK_URL}Outbound write: the full brief (10k–16k chars) leaves the machine.Unset → the grounding link is inert
${STALE_ACK_URL}Outbound write: ticket id + reason + commit sha.Unset → the drift watcher is inert
Git forge tokenFine-grained PAT, contents:write + pull_requests:write on one repo, no Administration, no Workflows.Must be provisioned by a human
${NOTIFY_CHANNEL}Chat/webhook for run summaries. Summaries leave the machine.Optional; omit for local-only logs
Browser profile (QA step only)A browser profile logged into a dedicated test account on a preview environment — never a personal profile, never a production account. It carries real session cookies. If a CAPTCHA, bot check or rate limit appears, the run stops and hands over to a human; behavior is never adapted to get past a defense.Opt-in. Skip the QA link entirely and the rest still works
Persisted on diskIndexes memory/pr-index.json, memory/qa-index.json, memory/remediation-index.json. QA screenshots under work/qa-runs/PR-/. Plus the journal memory/YYYY-MM-DD.md.Local only. Retention is executed by scripts/retention.py (§Retention), not just declared: the three indexes are purged when the PR closes, screenshots deleted at close and capped at 7 days. The journal is deliberately out of retention scope — it is the audit trail, and holds only ids and PR URLs, never personal data (§Retention)

Data minimization is a default, not your problem to solve. A brief cites code, never people: never copy a requester's name, email address, or any other personal identifier into a brief, a payload, a PR description, or a screenshot. Honor your forge's and hosting provider's ToS, and the law that applies to you (GDPR where applicable). Never point the QA link at a production tenant.

When to Use

User saysAction
"make an agent that opens PRs from our board / Linear / Jira"Build the whole loop below, starting at §Contract — not at the PR.
"the agent opened the same PR twice"§Anti-duplicate — usually the server ack is missing.
"the agent's PRs are garbage / off-scope"§Grounding. The brief is the product.
"our QA bot labels PRs and nothing happens"A state with no consumer. references/browser-qa.md.
"the agent coded against files that no longer exist"§Brief staleness.
"should the agent merge if CI is green?"No. Never. See the coda.

The closed loop

  human triage (prioritize)          ← the cheapest guardrail there is
        │
        ▼
  [GROUND] raw request ──read-only code──▶ anchored brief ──▶ grounding-ack
        │
        ▼
  [RUN] queue ──▶ head of queue only ──▶ worktree ──▶ implement ──▶ blocking QA ──▶ draft PR ──▶ ack
        │                                                                                        │
        ▼                                                                                        ▼
  [STEWARD] merge integration branch in, map inter-PR conflicts        [QA] browser-test the preview → verdict + label
        │                                                                                        │
        ▼                                                                                        ▼
  [DRIFT] audit briefs still in queue ──STALE──▶ back to GROUND        [REMEDIATE] consume needs-work → fix on PR branch → back to QA

Read the arrows as consumers: stale-ack → grounding, needs-work → remediation. Ship a link whose output nobody reads and you have built the graveyard.

Contract — queue and ack

Design the HTTP surface first; the agent is the easy part. Full spec + typed errors: references/queue-contract.md.

curl -fsS --max-time 30 "${QUEUE_URL}" -H "Authorization: Bearer ${AGENT_API_TOKEN}"
# Output: {"ok":true,"count":2,"arrangementUpdatedAt":"2026-07-16T09:00:00.000Z","tickets":[
#   {"id":"tkt-401","title":"…","prompt":"…","status":"ready","column":"P0","globalOrder":0}, … ]}
RuleWhy it is non-negotiable
globalOrder is a total order computed server-side (P0→P3, then array position)The board is the single source of order. The agent never re-sorts, never skips.
GET is read-only and safe to pollPreflights can poll a cheap count every 10-15 min with no LLM involved
Ack is conditional + idempotent: ready → pr-opened only if still readyTwo overlapping runs cannot both consume the ticket. Repeat calls return {ok:true,idempotent:true}.
401 on a timing-safe comparison (crypto.timingSafeEqual)A naive === on a shared secret leaks its length/prefix under repeated probing
Unknown id → 404 unknown_card, never a silent 200A lying ack is worse than a failing one

"Ready" = status AND prioritized column. Both, or the ticket is invisible: a card sitting in INBOX is invisible to the agent whatever its status, and a human dragging it to P0..P3 is the explicit authorization to spend agent time. That drag is the human triage gate — the cheapest guardrail in the system. Empty or missing arrangement → empty queue → the agent does nothing. Fail-safe, not fail-open.

The executable grounding gate

Never implement a raw request. Ground it: read the real code read-only, verify every anchor (path, line, helper, convention) before writing it down, and emit a brief so self-contained that another agent can implement it without ever reading the original request. Method + payload template: references/grounding-brief.md. Checks run in one canonical order — the same in the script, in the server, and in the table below; any other order and the two sides are not mirrors.

#GateThresholdEnforced
1Mandated first lineexactly Read CLAUDE.md AND AGENTS.md at the root of ${TARGET_REPO} first, with your repo name substitutedclient + server (startsWith)
2Brief length≥ 3000 chars, target 10k–16kclient + server
3technical non-emptyverified paths + line ranges + helpers to reuseclient + server
4definition non-emptyacceptance criteria + explicit out-of-scopeclient + server
Unverifiable anchorno ack — the card stays in the grounding queue, alert a humanagent rule

Duplicate the gate on purpose. Locally you fail before the network call; server-side because your endpoint must never depend on the politeness of its client — a retry script or a hand-written curl will send garbage eventually. Configure the prefix first. ${TARGET_REPO} is a placeholder: export BRIEF_GATE_PREFIX with your repo name substituted, byte-identical to the string your server checks. The script refuses to run on a placeholder — or on an empty value, since startswith("") matches every prompt and would report a disabled gate as a passed one.

export BRIEF_GATE_PREFIX="Read CLAUDE.md AND AGENTS.md at the root of app-example first"
python3 scripts/brief-gate.py tkt-401 payload.json prompt.txt > body.json
# Output (rejected, exit 1): brief-gate: prompt too short (1180 < 3000 chars). Flesh out the brief: target 10000-16000 -- NOTHING was sent.
# Output (accepted, exit 0): stdout → body.json = {"card_id":…,"payload":{…},"prompt":"…"}
#   plus, on stderr, if the brief is under the 10k target: "brief-gate: note - prompt is 4061 chars,
#   outside the 10000-16000 target (gate still green)". Judge the exit code, never the silence.
curl -fsS -X POST "${GROUNDING_ACK_URL}" -H "Authorization: Bearer ${AGENT_API_TOKEN}" \
  -H "Content-Type: application/json" -d @body.json
# Output: {"ok":true,"status":"ready"}
# Output (server mirror rejects): 400 {"ok":false,"error":"grounding_too_thin"}
#   Same predicate, both sides, or it is not a gate.

The run, step by step

  1. Fetch. count == 0 → answer QUEUE_EMPTY, stop. HTTP/auth error → STOP + alert, touch nothing.
  2. Select globalOrder == 0 only. One ticket per run. No batch, no parallel, no skipping.
  3. Anti-duplicate before any work (§below).
  4. Deterministic branch = pure function of the ticket: agent/-. Same ticket ⇒ same branch, forever. This is what makes step 3 possible.
  5. Clean worktree from the up-to-date integration branch (git fetch origin then branch off origin/) — never from a stale local state. Gotcha: if node_modules is a symlink in the worktree, bundlers fail with an unrelated error → rm node_modules && inside the worktree.
  6. Read the target repo's conventions BEFORE implementing: CLAUDE.md + AGENTS.md at its root. They outrank your habits.
  7. Implement strictly per the brief. No opportunistic refactor. Ambiguity or a red line in play → STOP + ask. Do not guess.
  8. Blocking QA before any commit: . One red = no PR, no ack.
  9. One draft PR per run.
  10. Ack, then bookkeeping: memory/pr-index.json, journal line, remove the worktree.
git config user.email          # Output: agent@example.com  ← must match the authenticated forge account
gh pr create --repo acme-corp/app-example --base  \
  --head "agent/${id}-${slug}" --draft --fill --label agent
# Output: https://github.com/acme-corp/app-example/pull/42
curl -fsS -X POST "${ACK_URL}" -H "Authorization: Bearer ${AGENT_API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d "$(python3 -c 'import json,sys;print(json.dumps({"card_id":sys.argv[1],"pr_url":sys.argv[2]}))' "$id" "$pr_url")"
# Output: {"ok":true,"status":"pr-opened","pr_url":"https://github.com/acme-corp/app-example/pull/42"}
# Build the body with python3, never by string-concatenating a title into JSON.

Git identity gotcha, root cause: a commit authored with a local email (*@Mac-*.local) is not linked to any forge account, so preview hosts refuse to build the PR ("No account was found matching the commit author email address"). The build never fails — it simply never happens, and the browser-QA link starves with no error to read. Check git config user.email before the first commit of every run.

Anti-duplicate — 4 locks, in order of strength

#LockStrengthSurvives
1Server conditional ackready → pr-opened only if still ready; next poll no longer lists itStrongest — makes duplicates impossiblea crashed run, a cloned workspace, a wiped disk, two agents racing
2One ticket/run (globalOrder=0)Serializes work; the next run picks up the new heada crash (state is server-side)
3Local index memory/pr-index.json, keyed by ticket id — entry: {"tkt-401":{"number":42,"pr_url":"…/pull/42","branch":"agent/tkt-401-invoice-filter","ts":"…"}}Fast, but the truth lives on a disk you might lose — never trust it alonenothing: a fresh machine forgets everything
4gh pr list --head agent/- --state all before any createCatches what the index forgota wiped disk (queries shared state)

Negative proof, at the permission layer: provision the PAT with contents:write + pull_requests:write and nothing else. Then gh pr merge fails with a permission error and git push origin is refused by branch protection. The agent doesn't merge because it cannot merge — not because it was asked nicely. Verify this before going autonomous: run gh pr merge once and confirm it fails.

The decision tree

SituationAction
Queue KO / bad BearerSTOP + alert. No PR, no ack, no work.
count = 0QUEUE_EMPTY, end. Silence is a valid outcome.
PR already exists for the ticketNever recreate. Never reopen a closed one. Ack if it was missed, finish action=skipped.
Checks redNo PR AND no ack. Clean the worktree, alert with a short error excerpt, tests_ok=false.
Scope doubt / red lineSTOP + alert. Do not touch the queue. We do not guess.

GOLDEN RULE — red = no PR AND no ack, the ticket stays in the queue. This one line is what makes the queue self-healing: a failed run leaves the world exactly as it found it, the ticket is still head-of-queue, and the next healthy run retries it. No retry logic, no dead-letter queue, no state machine. The design does not depend on the scheduler's retry — it depends on never acknowledging work that didn't happen.

The queue you do not control

The golden rule is purchased by the conditional ack (§Anti-duplicate, lock 1). Take that away and it inverts. Sooner or later the work source is a spreadsheet, a shared board, or someone else's API: no compare-and-swap, no conditional write, no transaction. Everything above still applies except the one line you most rely on.

You own the endpointThe store you do not control
Claim = ready → pr-opened only if still ready. Two runs cannot both win.Claim = a plain write. There is no predicate, so the lost race is undetectable at write time.
A crashed run acked nothing → the ticket is still head-of-queue → retry is free.A crashed run already wrote in_progress. Nothing rolls it back. The item is now claimed by a ghost owner, forever.
Failure is safe by default: do nothing, and the world is unchanged.Doing nothing leaks the item. Silence is not neutral.

So the rule flips: failure must be terminal, and explicitly written. "No ack" stops being self-healing the instant claiming stops being conditional — the row is already marked. Three consequences, none optional:

  1. Confirm the claim by reading it back. Write your claim (status=in_progress plus a runId that is yours), then re-read the row and compare both fields. A mismatch means another writer won — abandon, do not proceed. This read-back is the only race detection you get; there is no 409 coming.
  2. Write the failure. Wrap the run so every exit path — crash included — writes a terminal status with a short reason. An unwritten failure is an item claimed by nobody, and it is invisible: the row looks busy, not broken.
  3. Reclaim orphans by TTL, never by trust. The claim carries the instant it was taken. An in_progress older than your longest possible run is an orphan; reclaim it. Without this, one kill -9 removes an item from the world permanently and raises no error, ever.

A local lock does not fix this. A mkdir lock on your own disk serializes your runs and says nothing about a second machine, a re-provisioned host, or a human editing the row by hand. The read-back is what protects you; the lock only makes the race rarer — which is worse, because rare enough to pass testing is not the same as absent.

If you can put a conditional write behind the queue, do it. Everything in this section is the tax you pay for not having one.

Integration steward

RuleReason
Integrate by merge (git merge origin/), never rebase, never --forceRebasing a pushed PR branch rewrites history under an in-flight review and invalidates every QA verdict already keyed to a commit
Inter-PR conflict map: cross the files arrays of every open agent PR; any pair sharing ≥1 file is a potential conflictSequence them (oldest/highest priority first), comment "potential conflict with #N on path — integrate after", and do not heavily touch the downstream PR this run
Conflict too ambiguous → git merge --abort, comment, alertA conservatively resolved conflict is still a human decision
Re-run the blocking QA after the merge; red → do not push the merge
gh pr ready is forbidden. Signal readiness (comment + label), stay in draftLeaving draft is the human's move. That's the whole doctrine in one command.
Every comment the loop posts opens by disclosing it is agent-written (> Automated — proposed by an agent, not a human review.)The PR is labelled agent, but a comment read in isolation isn't. A reader must never mistake a proposal for a human review — and if anyone asks whether this is automated, the answer is yes.
gh pr list --repo acme-corp/app-example --label agent --state open \
  --json number,headRefName,headRefOid,files,updatedAt
# Output: [{"number":42,"headRefName":"agent/tkt-401-invoice-filter","headRefOid":"9f2c1ab…","files":[…]}]

Idempotence by headSha

One verdict per commit. One remediation attempt per commit. Key every index @:

IndexKeyMeaning
qa-index.json42@9f2c1abthis exact commit already has a QA verdict → don't re-test
remediation-index.json42@9f2c1abthis exact commit already got one fix attempt → don't retry, a new commit reopens the right

A new commit changes headSha, so the QA label goes stale automatically — no cleanup job, no TTL. Labels are human signals, NOT the source of idempotence: qa-needs-work present but the current headSha absent from qa-index.json = stale → QA re-tests, remediation skips. Without this key, QA and remediation fight over the same PR forever, each undoing the other's premise. Whoever laid down the verdict removes it: remediation pushes and stays silent on labels; QA re-verdicts the new commit and replaces the label. Every run that examines a PR writes an entry — pushed on success, or one of five named escalations, the agent's explicit right not to improvise (without it, an agent facing a hard bug ships a plausible-looking wide fix). Both: references/browser-qa.md.

Brief staleness is a first-class state

A brief anchored in code that has since moved is a loaded gun pointed at the implementation link. Audit briefs still in the queue against the integration branch, read-only. Full method: references/brief-staleness.md.

TechniqueWhy not the obvious thing
test -e on the path, never grep the path as textRoute paths contain dynamic segments ([id], [locale]) — grep produces false negatives
Path mapping before verdict (lib/x → also test src/lib/x)Briefs cite import-alias paths, not disk paths
Judge only anchors presented as EXISTING ("modify", "reuse", "already", "lines X-Y")A file described as "to create" is not drift when absent
Symbol must have a definition (`functionconst
git log -S '' --all when absent from current codeDistinguishes "renamed/removed" from "never existed"
Verdicts: FRESH / MINOR_DRIFT / STALELine drift alone with file+symbol present = MINOR_DRIFT, not STALE

The technique worth stealing — the skeptical refutation pass. Before any destructive verdict, hand the candidate STALE to a second agent whose only mission is to prove the first wrong (basename, mapped variants, rename, barrel re-export, git log --diff-filter=D, --follow, -S). Refuted → MINOR_DRIFT. Doubt = never STALE. A single agent asked to check itself confirms itself; it has already committed to the reasoning.

The drift watcher's only write is a conditional stale-ack (ready → needs-grounding). Never raw SQL. Never hand-patch a brief — flip the status, let grounding regenerate it. A pr-opened ticket is never re-grounded (action: "check_pr").

Retention — declared is not enforced

QA screenshots are authenticated-session data; indexes name PRs forever. A retention nobody runs is a claim, not a control — so run it weekly, from the repo root, on the loop's cron:

gh pr list --repo acme-corp/app-example --label agent --state all --json number,state > pr-states.json
python3 scripts/retention.py pr-states.json           # dry run: prints the plan, changes nothing
# Output: retention: DRY RUN (3 PRs known) / memory/qa-index.json: 3 -> 1 entries
#         work/qa-runs/PR-42: removing (PR MERGED) / work/qa-runs/PR-99: removing (older than 7 days)
# On stderr, if an entry cannot be expired: retention: WARN memory/pr-index.json: key tkt-401
#   carries no PR number, cannot expire -- add a "number" field to this entry.
python3 scripts/retention.py pr-states.json --apply    # same plan, executed

No network call — it consumes the states fetched above. It removes only PR- directories directly under work/qa-runs/. The two scopes do not have the same rule. Indexes: an entry whose PR is absent from the listing, or whose key resolves to no PR number, is kept (absence ≠ closure) and WARNed. Screenshots: a PR- directory goes as soon as its PR is no longer OPEN, and in every case at 7 days — open, closed or unknown alike. Screenshots carry authenticated-session data: 7 days is a hard ceiling, not a fallback, so a PR still in review on day 8 loses its evidence by design. It refuses to run from a root holding neither memory/ nor work/qa-runs/ (--root= for cron), because a purge that silently did nothing is the failure this section exists to prevent. The journal memory/YYYY-MM-DD.md is out of scope on purpose — it is the audit trail, holds ids and PR URLs and never personal data, and no code touches it: the enforcement claim is scoped to the three indexes and the screenshots.

An entry is expirable only if a PR number resolves from it — hence number on every pr-index entry (§Anti-duplicate lock 3): that index is keyed by ticket id, so tkt-401 alone resolves to nothing and the entry would live forever. qa-index/remediation-index resolve from their @ key. An unresolvable entry is kept and WARNed on stderr, never kept silently — a retention that quietly skips an index it claims to purge is the "declared, not enforced" failure this section exists to prevent. Grep your cron log for WARN.

Output format

Every run answers with the same block — short, factual, no dump, no secret, no PII:

 - 
Result: …
Item: …
Next action: …
Details: …

# Filled:
autopr - Done
Result: Draft PR opened and ticket acknowledged.
Item: tkt-401 - Filter invoices by status
Next action: Human review of the PR.
Details: branch=agent/tkt-401-invoice-filter, PR=…/pull/42, checks=green, action=created

Troubleshooting

SymptomRoot causeFix
Queue always empty, no errorAgent token ≠ server token → every GET is a 401 your script swallowsAssert 401 without Bearer and 200 with, as a deploy gate
Two PRs for one ticketAck is unconditional, or only the local index guardsMake the ack conditional server-side (lock 1)
gh pr create fails on --label agentThe label doesn't exist in the repogh label create agent — and the QA labels too (§First-run)
PRs are off-scopeThe agent implemented a raw request instead of a grounded briefThe grounding gate + "nothing beyond the brief" (§Grounding)
PRs are broken on arrivalChecks run after the PR, or not at allBlocking QA before the commit (§The run, step 8)
The agent merged somethingPermissions allowed itPAT without merge rights + the NEVER coda. Verify gh pr merge fails
No preview deploy, no error anywhereCommit authored with a local git email → the forge can't link it → the host never buildsgit config user.email before the first commit
PR stuck needs-work foreverNo consumer for the labelAdd remediation, or remove the label
QA and remediation loop on one PRIdempotence keyed by label instead of @Re-key both indexes
Briefs cite deleted filesNo drift watcher; briefs age silently in the queueAdd the staleness audit + refutation pass
Every brief passes the gate, even a garbage oneBRIEF_GATE_PREFIX resolved to empty (export X="$UNSET_VAR") → startswith("") is always trueFixed: the script now exits 1 on an empty prefix. Assert it in §First-run
Screenshots of closed PRs still on diskRetention declared but never executedscripts/retention.py … --apply, weekly (§Retention)
pr-index never shrinks, other indexes doEntries written without number → the ticket-id key resolves to no PR → kept foreverWrite number on every entry (§Anti-duplicate lock 3). The WARN on stderr names each unresolvable key

First-run checklist

  • Labels agent, qa-passed, qa-needs-work exist in the repo (gh label create agent) — gh pr create --label fails on a missing label.
  • BRIEF_GATE_PREFIX exported with your repo name substituted, byte-identical to the server's string.
  • GET ${QUEUE_URL}401 without Bearer, 200 with. Both asserted.
  • Ack tested twice on one id → second returns idempotent:true.
  • Local gate rejects a 1k-char brief without any network call; server returns 400 grounding_too_thin for the same input.
  • BRIEF_GATE_PREFIX="" → the gate refuses to run (exit 1). An empty gate must never look like a passed one.
  • python3 scripts/retention.py pr-states.json (dry run) prints a plan and changes nothing; --apply on a cron, weekly. Zero WARN on stderr — a WARN means something it claims to purge was not purged.
  • Run retention from /tmpexit 1, not a green no-op. Your cron's WorkingDirectory (or --root=) is the difference between a retention and a claim.
  • gh pr merge fails (negative proof). Release branch protected, force-push and deletion off.
  • Dry run on an empty queue → QUEUE_EMPTY, nothing created.
  • Dry run on one ticket, then immediate re-run → no duplicate.
  • Every state your loop can emit has exactly one consumer. List them. Any orphan = future graveyard.

Scope

This skill ONLY: reads a prioritized queue over an authenticated read-only endpoint; grounds briefs by reading code read-only; opens draft PRs on one repo from a deterministic branch; runs lint/type/test/build before proposing; browser-tests preview deploys non-destructively, on a dedicated test account, against test data; proposes verdicts and fixes, always disclosed as agent-written; acknowledges work that actually happened; talks to the four endpoints you configured, and to nothing else.

This skill NEVER: merges, marks a PR ready, pushes to an integration or release branch, force-pushes, rewrites history; acks a run whose checks were red; touches a ticket a human hasn't prioritized; invents an anchor it could not verify; clicks a destructive or irreversible action during QA (scan, delete, invite, payment) or points QA at a production tenant or a personal profile; works around a CAPTCHA, bot check or rate limit — if a challenge appears, it stops and hands over to a human; passes its output off as a human review, or denies being automated if asked; copies a requester's name, email or any personal identifier into a brief, a payload, a PR or a screenshot; keeps QA screenshots past the PR's close, or 7 days; hides a failure behind a fake success.

Related skills

Find why your productivity system keeps failing, then apply the smallest fix — capacity math, bottleneck routing, durable local notes.

by Iván854 installs69 stars

Query and manage Linear issues, projects, teams, cycles, labels, and comments through a managed OAuth GraphQL endpoint.

by byungkyu518 installs18 stars

Stores durable facts in a categorized, plain-markdown vault on disk, alongside your agent's built-in memory.

by Iván555 installs18 stars

Join a video meeting as an AI bot with voice, avatar, and screenshare across four operating modes.

by johnpatternai21 installs8 stars

Run Git operations — commits, branches, merges, rebases, conflict resolution, and recovery — with safety rules enforced.

by Iván532 installs31 stars

More from alexbloch-ia

Browse all skills

Operate a Reddit account from an agent or cron without tripping automod, shadowbans, or sub bans.

by alexbloch-ia19 installs1 stars

Operating doctrine for scheduled X/Twitter account automation that protects account reputation over posting volume.

by alexbloch-ia15 installs1 stars

Reply to Instagram DMs and comments through Meta Business Suite, staying inside quotas to avoid action blocks.

by alexbloch-ia14 installs1 stars

Run WhatsApp Business outbound without losing quality score — 24h window, approved templates, duplicate guard. Use when answering inbound leads or sending fo...

by alexbloch-ia15 installs1 stars

Run a TikTok Business account from cron — inbound DM/comment replies, quotas, phase gates, stop-on-block. Use when scheduling TikTok replies. Trigger on "tik...

by alexbloch-ia14 installs

Operating doctrine for Facebook Page automation — careful brand-page pattern via Meta Business Suite, role separation, page-vs-profile-vs-group surface aware...

by alexbloch-ia17 installs1 stars