A PR agency
in a box.

Seven scheduled agents research outlets, write the pitch, send it, handle the reply, and chase exactly twice. Open source. Self-hosted. Apache 2.0.

A miniature newsroom inside an open archive box: seven lit desks arranged in a ring, each connected by a thin line to a single filing cabinet at the center.

Seven desks · one filing cabinet · the whole agency

A rolodex tipped on its side at night, its cards spilling out and dissolving into scattered dots as they fall.

What context loss looks like

The problem

One long-running agent holding a campaign in its context fills up, summarizes its history, and drops the one line that matters: did we already email this person?

It does not fail loudly. It pitches someone a second time, warmly, having lost the memory that there was a first time.

The agency

Seven lanes, one job each, in the order a real agency works.

Research in the morning, outreach after it, oversight at the end of the day. The agents never talk to each other. The strategist hands work to outreach by writing a row in the database; any lane escalates to the manager by appending a line to a file.

Every lane is stateless. It starts with no memory of yesterday, reconstructs what it needs from the CRM, does one job, records it, and exits.

  1. weekdays

    Requests — answers journalists

    Works journalist source-request digests, because a popular query collects hundreds of replies in the first hour.

  2. daily

    Strategist — reads the room

    Researches each outlet's recent work, picks the one angle that fits, and writes a pitch brief. Sends nothing.

  3. daily

    Outreach — writes and sends

    Sends pitches from those briefs, answers inbound replies, and follows up twice — day 5, then day 12 — after which the thread is closed.

  4. Mon / Wed / Fri

    Bookings — works the platforms

    Drives booking platforms through a real browser, capped at ten messages a run. Off by default; it needs an adapter you write.

  5. daily

    Inbox — afternoon triage

    Handles inbound mail only. No new cold pitches, no follow-ups.

  6. daily

    Manager — runs the agency

    Reviews every lane, reads two or three of the day's outbound messages for quality, enforces the budget, and is the only lane that emails the client.

  7. on demand

    Writer — drafts the byline

    Drafts a bylined article against an accepted commission and leaves it in drafts/ for review. Never sends.

  8. every approach

    Critic — kills generic pitches

    Runs inside every lane that writes to an outlet. A veteran booker persona that reviews the draft before it goes out: one idea, something real and recent of the recipient's, one credential, 120 words for an email and 90 for a platform message. The lane revises until the verdict is SEND.

Architecture

No lane reads another lane.

A handoff written to disk survives a crash. A handoff held in a conversation does not.

How the seven lanes coordinate Seven independent lanes — requests, strategist, outreach, bookings, inbox, manager and writer — sit in a row with no connections between them. Every lane has a single line running downward into one shared collector, which feeds the campaign CRM, a SQLite file holding targets, threads, messages, briefs and budget, and alongside it the mail provider, which holds server-side threads. SEVEN INDEPENDENT LANES requests 3× weekdays strategist 07:30 outreach 08:00 bookings Mon / Wed / Fri inbox 16:00 manager 19:00 writer on demand Campaign CRM sqlite · one file · the only memory targets threads messages briefs budget approvals Mail provider server-side threads months later, same conversation EVERY EDGE ENDS IN THE STORE · NO LANE READS ANOTHER
  • requests 3× weekdays
  • strategist 07:30
  • outreach 08:00
  • bookings Mon / Wed / Fri
  • inbox 16:00
  • manager 19:00
  • writer on demand
seven independent lanes
no edges between them

Campaign CRM

sqlite · one file · the only memory

targets · threads · messages
briefs · budget · approvals

Mail provider

server-side threads

The strategist hands work to outreach by writing a row in the database. Any lane escalates to the manager by appending a line to a file. Nothing is passed between lanes in a conversation, so nothing is lost when one exits.

How it works

Three steps, then it runs itself.

  1. STEP 1

    Describe the client

    Write positioning.md — the outer bound of what may be claimed — plus policies.md and a target list.

  2. STEP 2

    Run a lane by hand

    New campaigns default to a provider that sends nothing and writes every would-be message to logs/outbox/. Read them before you go live.

  3. STEP 3

    Put it on cron

    publicist crontab prints the lines. From then on the agency runs itself and the manager reports on Fridays.

bash

$ pip install git+https://github.com/chasewhughes/publicist
$ publicist init ~/my-campaign
$ cd ~/my-campaign && publicist crm init
$ publicist doctor
$ publicist run strategist
$ publicist crontab

The guarantees

Enforced in code and covered by tests, not requested in a prompt.

A vault door standing ajar in a dark wall, amber light seeping through the gap, surrounded by small gears.

No target is pitched twice

crm claim is a conditional UPDATE, so only one caller can win. A run that crashes mid-send is recovered by checking whether a thread exists — never by guessing.

One door closes all the others

crm already-reached matches on normalized name, domain and address across every lane, and errs deliberately toward false positives. Silence from a target is not permission to knock again.

The sending ramp is not advisory

Eight cold emails a day in week one, rising to forty. A new domain that opens at forty gets filtered, permanently on any useful timescale.

Two lanes never run at once

A campaign-wide flock held for the whole run, released by the kernel if the process dies. A crashed lane cannot wedge the campaign.

A reply cancels the chase

Logging an inbound message clears the follow-up timer in the same transaction. Nobody who answered gets nudged.

What it plugs into

Two requirements. Everything else is optional.

Providers are pluggable — a new mail backend is four methods.

Claude Agent SDKRequired Runs every lane.
SQLiteRequired The whole CRM, one file.
AgentMailOptional Real server-side email threading.
Podcast IndexOptional Four million feeds to search.
PlaywrightOptional The browser lane for API-less platforms.
cronOptional publicist crontab prints the lines.

Python 3.11+ and access to Claude, via a Claude Code subscription login or an ANTHROPIC_API_KEY.

Brand icons courtesy of Simple Icons (CC0-1.0). All marks are the property of their respective owners; their presence indicates interoperability, not endorsement or affiliation.

Read the code before you trust it.

It is alpha: the orchestration core has run a real campaign daily since July 2026, and the packaging is new. Apache 2.0, self-hosted, nothing to sign up for.

Star it on GitHub