5.6 · Orchestration

5.6 · 25 min

Orchestration

The report drafter works when you run it by hand. That is a demo, not an Agent OS. The thing that makes it a system is that it runs Monday at 6am whether or not anyone remembers, drafts six clients' reports before Grace has coffee, and leaves a log of exactly what it did. Orchestration is the layer that runs the workers without you, and its two jobs are getting them started (triggers) and being able to prove what happened (the run-log). Get this layer wrong and you either have workers nobody runs or workers running unaccountably, and the second is worse.

Triggers, matched per process

Four trigger types, and the process spec tells you which one fits.

  • Schedule. For work that recurs on a clock: the weekly report drafter runs Monday morning. This is the most common Agent OS trigger.
  • Webhook. For work that reacts to an event elsewhere: a new lead hits Meridian's website form, which fires the lead triage worker. The event happens outside your runner and pushes in.
  • Inbox. For work triggered by a message arriving: an email to a monitored address, a new file in a folder. Really a webhook whose source is a mailbox.
  • Manual. For work a human kicks off deliberately: Grace clicks "draft Northwind's report now" off-cycle. Every scheduled worker should also be manually triggerable, for testing and for the off-cycle case.

Meridian's three map cleanly: report drafter on a schedule, lead triage on a webhook, meeting-to-tasks on an inbox/webhook (a new Fathom transcript posted). The recommendation for the runner is the one you already have: GitHub Actions, because the workers live in a git repo, the 3.7 eval gate already runs there, and it costs nothing to start. But you must know its real limits before you promise a client a schedule.

Know your runner's real limits

Honesty about the runner is part of the job, because a schedule you oversell breaks at 6am and the client remembers. As of July 2026, GitHub Actions' scheduling has specific, documented edges:

  • Schedules use POSIX cron and run in UTC by default. The shortest interval is five minutes, and a schedule trigger can be delayed under high load (the top of every hour is worst), so it is not a real-time or exactly-on-time mechanism. For a Monday-6am report draft that a human reviews by noon, a few minutes of slack is fine; for anything needing sub-minute or guaranteed timing, GitHub Actions cron is the wrong tool and a small always-on service or a cloud scheduler is right.
  • Scheduled workflows run only on the default branch, and on public repos they auto-disable after 60 days of no activity. Meridian's knowledge repo should be private, which avoids the auto-disable, but you plan around the branch rule.
  • Manual and event triggers are separate mechanisms: workflow_dispatch fires a run manually (from the UI, the CLI, or the API), and repository_dispatch lets an external system trigger a run by POSTing an event to the GitHub API, which is how an outside form submission reaches your triage worker.

None of this disqualifies the runner; it scopes what you can promise. The report drafter's SLA (drafted by Monday 9am) survives a few minutes of cron slack with room to spare, and you say so to Grace instead of implying millisecond precision you do not have.

The honest boundary: where GHL, Zapier, and n8n sit

This is the section a sharp operator will test you on, so no strawmen. GoHighLevel, Zapier, and n8n are good tools, and they have a real, legitimate place in an Agent OS. That place is trigger-and-transport around your agent workers, not the reasoning inside them.

  • What they are genuinely great at. Catching events (a GHL form submission, a Calendly booking, a new row in a sheet) and moving data between apps on a fixed path, across thousands of integrations, with no code. Zapier's breadth (9,000+ apps) and GHL's tight grip on the marketing-CRM funnel are real advantages for the trigger and the delivery. When Meridian's website form is submitted, a GHL or Zapier workflow catching that event and POSTing it to your triage worker's webhook is exactly the right use. When the worker returns a drafted follow-up, a Zap routing that draft into the review queue or a Slack channel is again exactly right.
  • What has genuinely changed, stated fairly. These tools now ship their own AI. GHL has Conversation AI, Voice AI, and Workflow AI; Zapier has AI actions and agents. For conversational appointment-setting inside GHL's ecosystem, or a quick AI step inside a Zap, they can be the right call, and pretending otherwise is the strawman the operator will catch. The distinction is not "they cannot do AI."
  • Where the boundary actually is. The judgment-heavy, verifiable, higher-blast-radius work belongs in a scoped worker you control, because that is where you own four things the closed workflow tools do not give you: a typed output contract, a 3.7-style eval gate that graduates autonomy on evidence, least-privilege scoping down to one token and one client's data, and a single version-controlled knowledge source of truth. When the reasoning lives inside a closed workflow builder, you cannot eval it the way 3.7 demands, cannot scope its data access the way 5.4 demands, and cannot point it at the 5.3 knowledge repo as the one source of truth. So you keep the report narrative, the lead classification with a defensible reason, and the meeting-to-tasks extraction in your workers, and you let GHL or Zapier or n8n do what they are best at on either side: fire the trigger, move the data, deliver the result.

n8n earns its own note because it is self-hostable: a team that wants to own its automation infrastructure and keep data in-house can run n8n on its own server, and its error-workflow feature (a workflow that runs when another fails, to alert or recover) is a legitimate part of the failure design lesson 5.7 covers. Same boundary, though: n8n triggers, transports, and alerts around your workers; the evaluable judgment stays in the workers.

Idempotency and the run-log

Two properties turn a script-on-a-timer into an accountable system.

Idempotency. A trigger can fire twice: a webhook retries, a schedule overlaps a slow run, a human clicks the button after the cron already went. Every run carries an idempotency key derived from its input (client + week for a report, the lead id for triage, the transcript id for meeting-to-tasks), and the orchestration checks the run-log before starting: if this key already produced a draft, it does not produce a second one. Without this, a retried lead webhook drafts two follow-ups and a reviewer sends both, which is how an Agent OS emails a prospect twice and looks broken.

The run-log as a first-class artifact. Every run, success or failure, appends one record. This is not a debug convenience, it is the substrate for the audit log (5.7) and the scorecard (5.8), so it is designed, not bolted on.

run-log record (one line per run)typescript
interface RunRecord {
runId: string;          // unique per execution
worker: string;         // "report-drafter" | "lead-triage" | ...
idempotencyKey: string; // "northwind:2026-06-29" -> dedupe key
trigger: "schedule" | "webhook" | "inbox" | "manual";
startedAt: string;      // ISO timestamp
status: "success" | "failed" | "skipped_duplicate";
costUsd: number;        // from the SDK result message
numTurns: number;       // drift signal (2.11)
outputRef: string;      // path/id of the draft on the review surface
error?: string;         // subtype + message when failed
}

That record is enough to answer every question the next three weeks will raise: did Northwind's report run, what did it cost, is the cost drifting (2.11's warning), where is the draft, and if it failed, why. Append it on every run before you do anything else with the output, so a crash after the work still leaves a trace.

Lablab-5-6Schedule the drafter and webhook-trigger triage, both logging runs

Goal: Put the report drafter on a schedule and the lead triage worker behind a webhook, make both idempotent, and have both append to a run-log.

Prereqs: the report drafter from 5.5, a lead triage worker (build a minimal one on the same pattern if you have not), a GitHub repo for the Meridian workers, ANTHROPIC_API_KEY in the repo secrets.

  1. Add a run-log.jsonl and a small logRun(record) helper that appends one RunRecord per execution. Have both workers call it on success and on failure.
  2. Add idempotency: before a worker runs, compute its key (client:week or leadId) and check the run-log; if a success record with that key exists, log skipped_duplicate and exit without re-drafting.
  3. Schedule the drafter: a GitHub Actions workflow on schedule (a Monday-morning cron) that runs the drafter for each active client, plus workflow_dispatch so you can trigger it manually. Note in a comment that cron is UTC and can be delayed a few minutes, and that the SLA has slack for it.
  4. Webhook-trigger triage: add a repository_dispatch trigger to a triage workflow, and simulate an inbound lead by POSTing a repository_dispatch event to the GitHub API with a stand-in lead payload. Confirm the triage worker runs and drafts a follow-up to the review queue.
  5. Prove idempotency: fire the same lead webhook twice. The second run must log skipped_duplicate and produce no second follow-up draft.
  6. Prove the log is real: after a few runs (schedule, manual, duplicate), open run-log.jsonl and confirm every run left a record with cost, turns, status, and an output reference, including the skipped duplicate.

Verify

  • The drafter runs on the scheduled workflow and on a manual workflow_dispatch, and each run appends a RunRecord.
  • The triage worker runs when you POST a repository_dispatch event, and drafts a follow-up to the review queue.
  • Firing the same lead twice yields exactly one follow-up draft; the second run is logged skipped_duplicate.
  • run-log.jsonl has one line per run including cost, turns, status, trigger, and output reference, for successes, the duplicate, and any failure.
>Troubleshooting
  • The scheduled workflow never ran: check it is on the default branch (scheduled workflows only run there) and the cron is valid UTC; add a workflow_dispatch and trigger manually to confirm the job itself works before blaming the schedule.
  • The webhook did nothing: repository_dispatch needs a token with the right scope and a matching event_type; confirm the POST returned 204 and the workflow's types filter matches your event name.
  • Two follow-up drafts appeared: your idempotency check ran after the work, not before, or the key was not stable. Compute the key from the input and check the log first, then do the work.
  • A crashed run left no log line: you logged after producing output instead of before. Append the record (or at least a started/failed line) around the work so a crash still leaves a trace.

Knowledge check

Knowledge check

Q1Meridian's sharp founder, who knows GHL and Zapier well, asks why she should pay you to build agent workers when 'GHL and Zapier already have AI built in.' What is the honest answer that does not strawman those tools?
Q2You put the report drafter on a GitHub Actions schedule and promise Grace it drafts 'exactly at 6:00am every Monday.' Why is that promise a mistake, and what should you say instead?
Q3A lead's webhook retries and fires the triage worker twice for the same lead. Without idempotency, what happens, and what makes the run-log central to preventing it?

Sources