Content Autopilot
From a Reels request to a 9:16 MP4 render and approval, all in the operator's screen. Live SNS posting of newly made videos is still in testing
Staff request a Reel on the work tool's content screen, review the preview, approve it, and schedule the release. Generation costs money, and posting means public exposure. So every default is set to 'don't do it'. Posting is manual, recurring automations run as dry-run (they only simulate execution), and paid generation is off. Code drives the stage progression.
- A pure function decides the next action. plan(state) returns only a list of actions, and the executor applies them. Each stage has a deadline, and the parent issue wakes at the earliest deadline + 1 minute to check. On failure, it cancels and closes the sub-issue with no retry. It doesn't touch sub-issues that belong to another parent. So a rerun doesn't create a new pipeline.
- Format errors get exactly one repair. Only JSON format errors are eligible for repair. The previous output isn't resent; only the error list (up to 8 items, 120 chars each) is. At most 2 calls. A test pins down that if it's still wrong after the repair, it fails with no third call.
- Only real search results count as sources. A cited URL passes only if it appears in web tool results or the input. Evidence fields the model wrote itself are discarded. Claiming a search without one fails with web_research_unverified. Facts like product prices are still guarded only by prompt rules.
- A post with an unknown outcome is never resent. A start marker is written right before the POST. If an error occurs after sending, the item is left as publish_pending instead of failed. On restart, if it was already posted, it's closed as already_published. For Blotato, the submission ID is saved immediately, so a later wake-up only queries the status.
- The DB hands out publish slots. The claim function locks the row with FOR UPDATE, takes an account advisory lock, checks the daily cap, then issues a 30–900 second lease token (a lock marker borrowed for a short time). I didn't go with an in-memory app lock. However many processes run, the decision happens in one place: the DB.
- One ledger enforces two policies. LiteLLM and the direct Vertex path add costs to the same Redis key. That closed the gap where the direct path bypassed the proxy cap. If the ledger can't be read, individual calls pass and recurring automation starts are held. Reads use a Redis user with no write permission.
- Render retries share one deadline. If the LLM-written motion plan fails the HyperFrames check, it re-renders with the default template. Both attempts share one deadline, so a retry doesn't double the wait time. Renders run one at a time.
- The file to post is pinned by checksum. Right before posting, the Drive file's mime, size, md5, and location are checked again. The QA gate uses ffprobe to check 60 seconds, 1080×1920, 100MB, and h264, and for AI video it looks for the AI marker in every frame. Uploads read 8MiB at a time and resume on a 308 response (the resume signal).
- 617 test files