Skip to content

The GOVENANT Standard — Part 3: The Duty Roster ⭐

The coverage contract. Law 3 made concrete: every role’s job, decomposed into declared duties — trigger, expected outcome, SLA — stored as data, executed by the dispatcher, and diffed against reality every day. Exactly as a human job description decomposes into a calendar, so that we know for a fact there is full coverage — and, when there isn’t, exactly which duty went silent and why.

This is the forward-looking half of governance. The audit (Part 9) proves things about work that happened; the roster declares what should happen, which is the only way silence becomes detectable per-duty rather than discoverable per-post-mortem.


3.1 Why a roster, not just crons

Every pre-roster OCMAS implementation scatters “what the org is supposed to do” across three places, none of them governable:

  1. Cron entries in deploy config — invisible to tenants, unauditable per-role, uneditable without a deploy, and their data-dependency ordering is implicit (the reference system ran its grader cron 12 hours before the metric writer for weeks; zero grades ever persisted while every run logged success).
  2. Charters — say what a role owns, never when it acts or what each beat must produce.
  3. Prompts — describe responsibilities the substrate never checks (Law 1 violation by definition).

The result: the org can be fully “governed” under Laws 1–2 and still quietly not do most of its job. Nobody can answer “is every responsibility of the CMO covered by some trigger?” — the exact question you would ask about a human employee whose calendar is empty.

The roster fixes this by making the schedule a first-class, versioned, human-approvable registry — and by requiring every duty to carry a delivery assertion, so the roster can never become decorative (see §3.9).

3.2 The duty

A duty is one declared unit of a role’s job:

CREATE TABLE role_duties (
id TEXT PRIMARY KEY,
scope_kind TEXT NOT NULL DEFAULT 'tenant', scope_id TEXT,
role_key TEXT NOT NULL,
duty_key TEXT NOT NULL, -- 'review_overnight_metrics', 'drain_reply_queue'
title TEXT NOT NULL,
description TEXT, -- the human-readable job-description line
-- WHEN: exactly one trigger kind
trigger_kind TEXT NOT NULL CHECK (trigger_kind IN ('cron','event','dependency')),
trigger_spec TEXT NOT NULL, -- cron expr | event type constant | upstream duty_key
-- WHAT: the delivery assertion (Law 2 — a duty without one is invalid at write time)
expected_outcome TEXT NOT NULL, -- outcome vocabulary term ('emails_sent','report_posted','none_checked')
outcome_assertion TEXT, -- machine-checkable predicate against the DB
sla_minutes INTEGER NOT NULL DEFAULT 60, -- outcome must exist within SLA of firing
-- HOW MUCH: pacing and budget
max_cost_usd REAL, -- per-firing spend ceiling
priority INTEGER NOT NULL DEFAULT 50, -- preemption ordering (events > calendar > backlog)
-- WHAT IT TOUCHES: makes coverage statically checkable
reads TEXT, -- JSON array of data sources consumed
levers TEXT, -- JSON array of levers this duty may pull (⊆ charter.owns)
charter_ref TEXT, -- which charter responsibility this duty covers
-- GOVERNANCE
version INTEGER NOT NULL DEFAULT 1,
source TEXT NOT NULL DEFAULT 'default', -- default | agent | human (human edit locks)
active INTEGER NOT NULL DEFAULT 1,
escalation TEXT, -- who is woken on a missed SLA
created_by TEXT, created_at TEXT,
UNIQUE(scope_kind, scope_id, role_key, duty_key)
);
-- Every firing is ledgered — the coverage side of the append-only record.
CREATE TABLE duty_runs (
id TEXT PRIMARY KEY,
duty_id TEXT NOT NULL REFERENCES role_duties(id),
scope_id TEXT, role_key TEXT NOT NULL,
due_at TEXT NOT NULL,
fired_at TEXT,
resolution TEXT CHECK (resolution IN
('delivered', -- outcome_assertion verified within SLA
'skipped', -- sensor pass found nothing actionable — reason REQUIRED
'blocked', -- attempted, structurally prevented — reason REQUIRED
'missed', -- never fired / SLA breached — written by the conductor, never silent
'preempted')), -- displaced by a higher-priority interrupt; rescheduled
reason TEXT, -- machine-readable; NULL only allowed on 'delivered'
outcome_ref TEXT, -- ID of the verified outcome row (when delivered)
cost_usd REAL,
resolved_at TEXT
);

Two schema-level rules do most of the governing:

  • expected_outcome is NOT NULL. A duty that asserts nothing is rejected at write time. Even a pure monitoring duty asserts something (none_checked + an assertion that the check row exists).
  • reason is required on every non-delivered resolution. This is the block-reason lesson (anti-pattern AP-7 / the 100%-NULL block_reason regression) applied to the schedule layer: a silent skip is indistinguishable from a dead loop, so silent skips cannot be written.

3.3 The three trigger kinds — calendar, interrupts, queue

Pure time-based scheduling is brittle; pure event-driven scheduling can’t express rhythm. Human organizations run all three, and so does the roster:

KindHuman analogueSemantics
cronThe calendarThe rhythm: standing reviews, daily sweeps, weekly retros. Fires on the beat whether or not there is work — and records skipped(nothing_actionable) when there isn’t.
eventThe interruptA business moment (hot reply, CI failure, payment) wakes the duty immediately, preempting calendar work at lower priority. The duty is the subscriber of record for the event type — which structurally satisfies “every queue names its consumer.”
dependency”After the metrics land”Fires when an upstream duty resolves delivered. Replaces implicit cron-ordering with an explicit DAG.

Preemption rule: events > calendar > backlog, by priority. A preempted calendar duty is resolved preempted and rescheduled — never silently dropped.

stateDiagram-v2 [*] --> due : trigger fires (cron/event/dependency) due --> sensing : dispatcher claims (cheap pass) sensing --> acting : work found — full occupant run sensing --> skipped : nothing actionable\n(reason REQUIRED) acting --> delivered : outcome_assertion verified\nwithin SLA (outcome_ref set) acting --> blocked : structurally prevented\n(reason REQUIRED) due --> missed : SLA breached, never fired\n(written by conductor) due --> preempted : displaced by interrupt\n(auto-rescheduled) delivered --> [*] skipped --> [*] blocked --> escalated : per-duty escalation path missed --> escalated

3.4 Cheap-check / expensive-act

Do not literally simulate a human’s hours. The 15-minute-increment framing is a human metaphor; what it buys is expected output per beat, not busywork per beat. Implemented naively (a full LLM run per role per slot), a 16-role × 8-tenant roster would burn its budget performing attentiveness — performed autonomy with a calendar.

Every duty firing therefore runs in two phases:

  1. Sensor pass (cheap, deterministic where possible): query the duty’s declared reads — is there anything actionable? No LLM required for most duties (row counts, freshness checks, queue depths). If nothing: resolve skipped with reason, cost ≈ 0.
  2. Act pass (expensive, only when warranted): the full occupant run through the gauntlet (Part 5), bounded by max_cost_usd.

The roster is thus also the pacing and budget layer: per-duty spend ceilings roll up to per-role and per-tenant budgets, and a duty that chronically hits its ceiling is a visible, escalatable fact rather than a mystery line item.

3.5 The coverage invariants (statically checkable)

Because duties declare charter_ref, levers, reads, and dependencies, three audits become mechanical queries rather than investigations:

InvariantCheckFailure meaning
C1 — Full coverageEvery charter responsibility of every active role has ≥1 active duty (charter.owns[] ⟕ role_duties.charter_ref).An employee with a job description and an empty calendar. The job is not being done and nothing will ever notice.
C2 — No scope creepEvery duty’s levers[] ⊆ its role’s charter.owns[].The schedule authorizes what the constitution doesn’t — Law 1 violated by configuration.
C3 — Sound orderingThe dependency graph is acyclic AND every duty’s reads[] is produced by something that runs before it (topological check).The grader-before-metrics inversion, caught at config-write time instead of by a post-mortem.

Run C1–C3 on every roster write (reject on C2/C3, warn on C1) and nightly (drift detection).

3.6 Coverage measurement — the conductor, generalized

The pre-roster conductor was a dead-man’s switch on one beat (the weekly meeting). With the roster, every duty is a beat, and one nightly job answers Law 3 for the whole org:

For each role, per window (day / week):
duties_due = duty_runs expected in window
delivered = resolution = 'delivered' (outcome_ref verified — not self-reported)
reasoned_misses = skipped + blocked + preempted (reason present)
silent_misses = due with no duty_run row → conductor writes 'missed' + escalates
duty-delivery ratio = delivered / (delivered + acting-resolutions that failed)
coverage ratio = (delivered + reasoned_misses) / duties_due -- silence excluded by construction
  • The duty-delivery ratio is the single best per-role health number — surfaced on the org chart node, the Agent Configuration Schedule tab, and the daily standup.
  • Silent misses are the conductor’s job to make loud. The conductor MUST NOT share its subject’s failure mode (watchdog independence): it runs on independent scheduling and its own liveness is checked by the meta-health surface.
  • The daily standup is generated from the roster diff: “here is what your organization was supposed to do yesterday, what it delivered, what it skipped and why, and what went silent.” This replaces self-reported summaries with plan-vs-actual — a standup the substrate can’t flatter.

3.7 Roster authoring — generated, then governed

The roster is configuration, so it enters through the constitution plane:

  1. Draft: an LLM decomposes the role’s charter + an industry template into a proposed weekly roster (the “hour-by-hour CMO breakdown” exercise, productized). Templates ship per domain (GTM CMO, engineering reviewer, legal drafter) and per industry.
  2. Review: a human reviews and edits — this is the onboarding conversation: “here is your AI CMO’s proposed week; what would you change?” Every edit is source='human' and locks.
  3. Commit: the roster versions like a prompt (append-only, rollback). Agent-proposed roster changes (e.g. a role asking to add a duty it keeps doing ad-hoc) file through approval tiers — the org can propose reorganizing its own time, but never silently.
  4. Evolve: duty-delivery ratios feed back — a duty that never finds work is a candidate for a slower beat; a duty that always preempts is a candidate for higher priority. Retune proposals are approvable ledger entries (Part 6 §6.7).

3.8 The day view — coverage made seeable

The roster’s UI surface (Part 11) is the most legible governance artifact in the whole standard — the ant farm with a time axis:

  • Role day/week view: planned duties vs actual resolutions, color-coded (delivered / skipped / blocked / missed / preempted), with drill-in to the duty_run → action → outcome row chain.
  • Org coverage board: every role × every day, the coverage and duty-delivery ratios — one glance answers “is everyone doing their whole job?”
  • This page is a primary sales surface: “here is your AI employee’s job description and calendar; here is what she actually did against it” requires zero education to understand.
gantt dateFormat HH:mm axisFormat %H:%M title CMO — Tuesday (planned vs actual) section Calendar Review overnight metrics (delivered) :done, 08:00, 30m Content pipeline sweep (skipped - empty) :done, 09:00, 15m Campaign performance review (delivered) :done, 10:00, 45m Draft weekly content plan (blocked - gate) :crit, 13:00, 60m section Interrupts Hot reply event -> reply duty (delivered) :active, 10:45, 20m section Rhythm Standup contribution (delivered) :done, 08:45, 10m

3.9 The trap: schedule theater (anti-pattern AP-8)

A roster makes the org look more alive whether or not it is — a beautiful calendar where every slot “completes” with motion is performed autonomy squared. Three defenses are mandatory:

  1. No assertion, no duty (schema-enforced, §3.2). Every duty names a verifiable outcome.
  2. delivered means the outcome row exists — verified by the dispatcher against the DB, never by the occupant’s self-report. A duty_run whose outcome_ref doesn’t resolve to a real row is downgraded and counted as a failure.
  3. The audit diffs the roster against the record (Part 9, P5): high coverage with a low duty-delivery ratio, or a roster whose duties are all none_checked monitoring beats, are explicit findings.

See Part 10, AP-8 for probes.

3.10 Migration path (for existing implementations)

  1. Inventory existing cron entries → one duty each (trigger_kind='cron'), with the outcome each was implicitly supposed to produce made explicit.
  2. Inventory event subscriptions → trigger_kind='event' duties (this names a consumer for every event type — closing AP-3/AP-6 lanes as a side effect).
  3. Run C1 against charters → the uncovered-responsibility list is the backlog of duties to author.
  4. Point the dispatcher at the roster; retire deploy-config scheduling. (This migration is required for multi-tenancy anyway: customers cannot edit your deploy config.)
  5. Turn on the nightly coverage diff + conductor missed writes; add the ratios to the standup.