← All activity records

FACTUAL ACTIVITY RECORD · An activity record based on work that took place

Building a daily Runner for the AI talent editorial desk

Separating external intake from AI editing and handing off only validated drafts

Activity period:

Purpose

Automating the daily flow from news collection to an editorial draft required separating trusted processing for network access, credentials, and database operations from the AI that edits the article. Allowing the AI to control sources or destinations would weaken input-safety and publication-authority boundaries.

The goal was to have a parent Runner safely retrieve and normalize a NewsPacket, pass it to a fixed editorial Task, validate the result, and store it only as a draft. Approval and publication remain outside the automation, while failures can resume from the same input.

Implementation

A parent Runner was implemented in daily execution entry. It retries pending uploads first, checks for an existing run on the same day, then proceeds through authentication, loading approved sources, preparing a NewsPacket, AI editing, output validation, and draft submission in order.

  • Sources are restricted to HTTPS, with localhost, private, loopback, link-local, multicast, and reserved addresses rejected. Destinations are checked again after DNS resolution and at every redirect.
  • Wall-clock time, redirect count, bytes, and item count are bounded, while HTML, scripts, styles, and control characters are removed from feeds.
  • Full articles are not stored. Only the headline, URL, short excerpt, and publication time enter the NewsPacket, whose SHA-256 is recorded.

task executor resumes a fixed root Task instead of creating a new conversation each time. The AI receives only packet and structured-output paths, without access to the Runner, database, Web, MCP, network, or credentials. The child-process environment is narrowed, and timeouts terminate the entire process group.

workflow coordinator verifies that the NewsPacket is unchanged, packet and output paths are regular files rather than symbolic links, the JSON schema and Task identifier match, ten participants are present, every participant reviewed the same input hash, and all cited sources exist in the packet. Output that fails any condition is not submitted.

  • The Runner account is constrained by its binding and row-level security to reading approved sources and submitting drafts. It cannot edit, approve, or publish them.
  • A failed submission is preserved as upload_pending with packet and draft hashes, and the next run retries only the upload, oldest pending item first.
  • Authentication and state files use mode 0600, while a process lock, quarantine, date-based run key, and database uniqueness constraint prevent duplicate execution.

The launchd configuration starts the Runner daily at 10:00 and can catch up the current day after wake. A completed run for the same date is not processed again, while intermediate states preserve the stage needed for safe recovery.

The parent Runner did not submit AI output directly. One artifact boundary checked regular-file status, size, mutation during read, schema, and hash.

Daily editorial Runner flow at the cutoff

Rendering diagram…

What was confirmed

Verification results

The combined check passed with TypeScript type checking, 71 tests, and the production build.

Tests covered URL safety, feed normalization, state and file permissions, same-day idempotency, hash validation, timeouts, and draft-only authority.

The change scope and secret exposure were reviewed, confirming that local authentication files and build outputs were excluded from the deliverable.

The first end-to-end run stopped at AI-process startup because the configuration intended to disable MCP was rejected. No draft was submitted, approved, or published.

At the cutoff, the initial parent Runner and its static verification had been applied to the target, but a full operational cycle had not completed. Correcting the startup configuration and rerunning it belong to the next independent activity.

Basis for completion

The initial implementation was considered complete when the target contained safe intake, fixed-Task handoff, output validation, draft-only submission, retry, and idempotency controls, and passed type checking, 71 tests, and a build. End-to-end completion was not part of this basis; the startup failure remained explicitly unresolved at the cutoff.