Blog tool agent architecture
One blog task, from a Teamwork row to a filed document. First the process, then what powers each step, then what lives inside each of the four layers. One question decides where any piece of code belongs: does it show, store, do, or land?
Each Write Blog task becomes exactly one run. Clicking “Draft all” starts several of these side by side, so one post failing never stalls the others.
Only one step in the whole run is a model writing prose. Everything else is fixed code or your decision, which is what makes the output predictable.
what runs it, and what it is allowed to touch
| Step | Powered by | Kind | May touch |
|---|---|---|---|
| Confirm client, folder, topic | probe_client.py blog_index.py |
script | Reports dashboard, BMG-6 read-only, the topic sheet |
| Write the post | monthly-blogs skill via Claude |
model | Its own working folder, and the web for link checks |
| Mechanical check | blog_checks.py | script | The draft on disk |
| Queue for review | blogs_to_review.py | script | Cloudflare, as pending cards |
| Approve the words | You, in Review | gate | The card’s decision, nothing else |
| Release with minutes | You, on the card | gate | The release stamp and the minute count |
| Deliver | deliver_review.py | script | BMG-6, and Teamwork for the comment and the time |
| Ask for closure | You, on the row | gate | Nothing yet. It only records the request |
| Close in Teamwork | run_complete_task | script | The subtask and Log Efficiencies, never the parent |
| File the row away | blog_archive | script | Cloudflare, plus the spreadsheet mirror |
blog_tool.py) that is deliberately switched off: on 13 August the skill’s six Las Vegas posts passed the checker with zero flags while the tool-generated post missed two house rules. It stays available for batches where cost matters more than polish, which is why the draft step must remain a model step rather than being “simplified” into a script.Draws whatever state it is handed and captures your clicks. One file, about 5,000 lines, nine views.
each has a load and a render
| View | Shows |
|---|---|
| pages | Optimizations board |
| blogs | Monthly blog tasks |
| review | Drafts awaiting a decision |
| archive | What already shipped |
| work | Top 3 and the queue |
| GBP case tracker | |
| goals | Objectives |
| projects | Initiatives |
| system | This architecture, live |
all of it a cache of a response
| Global | Holds |
|---|---|
| DATA | Last /api/work payload |
| REVIEW | Batches, items, cards |
| BLOGS | Swept tasks and roster |
| PAGES | Page state rows |
| ARCHIVE | Delivered work by month |
| READ_* | Two-pane reader position |
needs_client” is enforced in this file. A crafted request to /api/review would bypass it. The server has to be the one that refuses.Holds every decision you have made, and the map of which state may follow which. Executes nothing and holds no client password.
access means you in a browser; bearer means the Studio
| Endpoint | Who calls it | What it is for |
|---|---|---|
| /api/review | You | Approve, drop, revise, release |
| /api/blogs | You + Studio | Swept tasks, client mapping, jobs |
| /api/page-state | You + Studio | What each page actually says now |
| /api/action | You + Studio | The legacy work queue |
| /api/archive | You + Studio | Delivered work, by month |
| /api/work | You | Top 3 and the queue |
| /api/sync | Studio | Pushes the analysis in |
| /api/system | You | This architecture, read-only |
| /api/goals | You | Objectives |
| /api/projects | You | Initiatives |
| /api/snooze | You | Hide an item for a while |
action.js composes the instruction text a job runs with, so the words a model receives are authored on Cloudflare. And GET /api/blogs calls out to reports.bmgdashboard.com live, making a page load depend on another system being up. Both belong on the Studio: the runner should push job state in, and Cloudflare should serve what it was last told.The only thing that does work, because it is the only thing holding credentials. There are two of these right now, and that is the problem the refactor exists to fix.
the engine being kept
| Module | Job |
|---|---|
| index.js | Claim a run, execute, report |
| claude.js | Build the prompt, run the model |
| grants.js | Scope tools to this run’s inputs |
| caps.js | Stop a runaway loop of calls |
| spend.js | Hold the DataForSEO dollar ceiling |
| files.js | Fetch inputs, upload deliverables |
| config.js | Read the vault key at moment of use |
| health.js | Report whether it is fit to work |
the engine being retired
| Behaviour | Detail |
|---|---|
| dispatch | 14 branches on a magic string |
| interval | Every 3 minutes, one action |
| lock | Stops overlapping cycles |
| recovery | Re-claims after a mid-run kill |
| dedupe | Completed ids cached locally |
| retry | Waits out a model session limit |
| history | None. No run events, no timeline |
| idempotency | None. Two clicks can do it twice |
the business rules, which are not moving
| Script | Pipeline | Does |
|---|---|---|
| deliver_review.py | Blogs | Replays approved bytes to the share |
| blog_checks.py | Blogs | Mechanical house-style check |
| probe_client.py | Blogs | Finds a client’s folder and topics |
| blog_index.py | Blogs | Reads and marks the topic sheet |
| build_topic_list.py | Blogs | Researches next month’s topics |
| blogs_to_review.py | Blogs | Pushes drafts to the review queue |
| apply_review.py | Optimizations | Writes approved changes to WordPress |
| reconcile.py | Optimizations | Re-reads live pages for staleness |
| find_to_plan.py | Optimizations | Sweeps optimize tasks |
| gmail_cases.py | Google cases | Syncs GBP case status from mail |
Where finished blog work comes to rest. Only the runner may touch either one.
the file share
| Fact | Value |
|---|---|
| mount | /Volumes/BMG_Customers |
| reached by | The runner only, over SMB |
| holds | The client-named document |
| folder | Per client, per month, resolved by probe rather than guessed |
| mirrored | A row in blog_archive, so the app can show what shipped without the share being mounted |
where the work is recorded
| Lands | Rule |
|---|---|
| QC comment | Posted once, never rewritten |
| actual time | To Jarrett, on the work date |
| efficiency | Always to Derek, at month end |
| completion | Log Efficiencies closes before Write Blog |
| the parent | Never closed. It carries other people’s subtasks |
WordPress and Gmail are landing surfaces too, but for different pipelines: WordPress for Optimizations and Gmail for the Google cases tracker. Same rule applies to both. Neither is part of the blog path.