Skip to content
C.W.K.
Stream
Lesson 01 of 04 · published

A Job Is a Report, Not a Turn

~14 min · master-chief, scheduling, notifications, cron, signal-to-noise

Level 0Visitor
0 XP0/33 lessons0/11 achievements
0/100 XP to next level100 XP to go0% complete

Two Ways Into One Room

Everything that reaches Master Chief arrives in one of two shapes. A turn is Dad saying something through a doorway and a brain answering: the engine admits it through the turn lock, replays the conversation, runs the model and stores both rows. A report is a line the engine appends on someone's behalf: no model runs, no replay is paid for, nothing waits for an answer. A turn occupies the room. A report leaves a note in it.

The whole family of sibling apps runs jobs on schedules: briefs, scans, triage passes, nightly checks. Every one of them is something Dad might want to know about, and almost none of them is Dad speaking. Get that distinction wrong and an endless conversation fills with work that was never a conversation. This track is about the machinery that keeps it right, and it starts with the two mistakes that showed why it was needed.

The Morning the Brief Moved In

Tidings, the family's news engine, sends a scheduled daily brief to Pippa through the older Ask door, with source: tidings. To the admission gate that looked exactly like Dad speaking from inside Tidings, so the brief ran inside the Chief every morning: the job prompt landed as a user row in Dad's room, followed by a 26,000-token reply. Track 2 walks the gate itself. What matters here is the fix (7252f738, 2026-10-08): a payload marked automated: true is a sibling's own job. It is never an app turn, it runs in the conversation it names, it leaves no doorway, and one addressed to the Chief gets a 409. The job had to declare what it was, because the door it used was Dad's door.

That fix keeps a job out. It says nothing about how the job's outcome gets in. That part has its own history.

From Every Result to Only the Ones That Need Dad

An earlier change had made cron results that notify Dad also post into the Chief, whole. Two things went wrong in quick succession. First, the gate read job.get('notify', 'telegram') != 'none'. The Soul Stream Wake jobs carry notify: null, and a default in get() only covers a missing key, so None sailed through and every hourly wake skip landed in the Chief (fixed in f2f1b14b: missing or null now means no notification). Second, even with that fixed, jobs that legitimately notify Dad were copying their full bodies into the room, "inbox is empty" included. Dad ruled on it (coop #345): the Chief receives only outcomes that need him, as one-line summaries that link to the job's own conversation. That ruling became d0eaaf16 (2026-10-08).

The Receipt, Not the Prose

How does the engine know whether a run needs Dad? It does not read the report and guess. A shared scheduler prompt asks every eligible worker to end with one final line, CRON_OUTCOME: {"status": ..., "summary": ...}, using one of eight statuses. Four are actionable: approval_required, failed, blocked, question. The other four (success, noop, skipped, empty) stay quiet. The parser reads only that terminal line; a missing or malformed receipt is unknown and puts nothing in the Chief (unless an owning engine's snapshot, below, says otherwise), and an actionable status with an empty summary is rejected as malformed. A valid receipt stays in the worker's transcript and is stripped before the job's own Telegram or email notification sees the body. A malformed one is left in place: the parser only reads, so the job's own notification, which follows the job's own policy, can still carry that broken line to the phone.

Two refinements keep it honest. Where an owning engine knows the truth better than the worker (the Herald and Journal approval queues), the engine snapshots the pending drafts before and after the run: new or revised drafts waiting for Dad count as approval_required, an unchanged backlog does not, and an unavailable snapshot is treated as unknown, never as an empty queue. And when the run itself crashes, the engine types the failure directly instead of asking a failed worker to write JSON.

The Gate and the Line

Before any of that, eligible() decides whether a job may report at all: it must be a Pippa job, its notify must be set to something other than missing, blank, null or none (that opt-out wins even over a push flag), and its chief_report must be actionable, the default (the only other legal value is never). Then one_line() builds what Dad sees: the job name, cut to 80 characters, a colon, the summary, cut to 200, and a link to the job's conversation. Brackets, parentheses, backticks and the other Markdown characters are stripped from both, so a job name cannot inject a second link or a code block into Dad's room. The full result never moves; it stays where the job ran.

A job never speaks in the room; it leaves a note on the door. The worker's transcript keeps everything. The shared room gets one bounded line, and only when the line asks something of Dad.

Code

Engine excerpt (verbatim, trimmed to the gate and the line): the outcome vocabulary, eligible() and one_line()·python
MARKER = "CRON_OUTCOME: "
ACTIONABLE = frozenset({"approval_required", "failed", "blocked", "question"})
STATUSES = ACTIONABLE | {"success", "noop", "skipped", "empty"}


def eligible(job: dict, soul: str | None) -> bool:
    """Notification opt-out wins, even over push or a structured failure."""
    return ((soul or "pippa") == "pippa"
            and str(job.get("notify") or "").strip().lower() not in {"", "none", "null"}
            and job.get("chief_report", "actionable") == "actionable")


def one_line(job: dict, outcome: Outcome, conversation_id: str) -> str:
    def plain(value: str, limit: int) -> str:
        # Names and summaries cannot inject another link or a Markdown block.
        value = re.sub(r"[\[\]()`*_<>\\]", "", " ".join(value.split()))
        return value if len(value) <= limit else value[:limit - 1] + "…"

    name = plain(str(job.get("name") or job["id"]), 80)
    summary = plain(outcome.summary, 200)
    link = f"/?conversation={quote(conversation_id, safe='')}"
    return f"{name}: {summary} [작업 대화]({link})"
An example run through the engine's own split_result() and one_line() (invented job; the link text is Korean because Dad reads it)·text
Worker's reply, last three lines:
  Checked last night's backup.
  The external disk was full; nothing was copied.
  CRON_OUTCOME: {"status": "failed", "summary": "백업 디스크가 가득 차서 어젯밤 백업이 안 됐어."}

split_result() ->
  body    = "Checked last night's backup.\nThe external disk was full; nothing was copied."
  outcome = Outcome(status='failed', summary='백업 디스크가 가득 차서 어젯밤 백업이 안 됐어.')  actionable=True

What lands in Master Chief:
  Nightly Backup Check: 백업 디스크가 가득 차서 어젯밤 백업이 안 됐어. [작업 대화](/?conversation=<job-conversation-id>)

Same job, quiet night:
  CRON_OUTCOME: {"status": "empty", "summary": ""}   -> actionable=False, nothing reaches the Chief
No receipt at all:
  -> outcome None (unknown), nothing reaches the Chief

External links

Exercise

Collect the last twenty automated notifications you received from your own systems: CI, cron mail, a monitoring bot, a scheduled script. Mark each one: did it ask you to decide or do something? For the ones that did not, write down which of the four quiet statuses (success, noop, skipped, empty) it really was. Then pick one job you own and change it to end with a single machine-readable receipt line, and make the notifier send only when that receipt says the outcome is actionable.
Hint
Make the absence of a receipt mean "unknown, send nothing", and test that case first. If you are afraid of missing a real failure that way, let the scheduler itself report crashes as a typed failure, the way the engine does, rather than trusting a failed run to describe itself.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.