project 09 — ~45 min
Build a daily briefing digest
How to get one honest page a day from an agent
by the end you can:
- Bind values into prompts with variables, and make the orchestrator consult before it writes → taught in 2.01
- Write a register as countable rules a transcript can be audited against → taught in 1.05
- End every fact line with its source, and report an empty sweep as a finding → taught in 2.05
- Compare two runs by holding four variables and changing one → taught in 1.04
you need: Node 22+ and the course repo · Gemini key (free)
A page you read every morning at 07:00 and never watched being written can be filled from memory on a quiet beat and read exactly as fluently as one built from that morning’s wire. If you were not there to watch the inference run, what would the page have to carry so you can tell? The answer lies in the output structure itself: every item ends in its source domain, a beat with nothing new is written down as empty, and the cap and the length are numbers you can count.
To enforce this standard, you convert the Global Synthesis Council into an unattended scheduled workflow that investigates the same beats each morning. The original council provides the foundation: an orchestrator configured to query its researcher before drafting a response, a researcher equipped with a single search tool, and a numeric constant bound into the prompt template. You modify this architecture by binding the date line and your chosen beats into the context, establishing an output contract that permits deterministic verification, and driving the process through a single cron schedule that runs in one-shot mode and exits cleanly. Over two consecutive mornings, the scheduled pipeline writes two dated files into briefings/, each containing a lead, at most N items with sources, and the empty beats named.
the run is a chain that ends in a file
The figure draws one Researcher call where a run with three beats makes three in sequence, and the file is written by the shell redirect, never by the model. The dated file names and the 07:00 are the course’s own settings, and nothing else on the page is measured from a run.
step one: run the council once, and read the file it came from
npm run chat:syndicate -- "What changed in AI regulation this week?"
This command executes one-shot mode against the default reference syndicate, the Council, which emits its answer and terminates execution. Next, inspect config/agents/examples/syndicate.yaml to observe three architectural details this project modifies. First, the variables mapping declares headline_count: 5, and the researcher prompt references {{headline_count}}, maintaining a single source of truth for numeric limits. Second, the orchestrator prompt explicitly mandates calling the researcher before synthesizing a response. This pre-generation query constraint prevents the orchestrator from answering from prior context rather than current tool data. Third, the researcher references google_search, a provider-specific legacy tool that the truth-arbitration project’s tool table replaces with web_search. The provider-agnostic web_search interface invokes the host model’s native search backend regardless of model provider.
step two: write the briefing as a contract
Create config/agents/briefing.yaml. Because the configuration loader checks the repository root before falling back to defaults, this file can be targeted directly by name.
syndicate_name: "Daily Briefing"
memory_system: "internal-only"
variables:
headline_count: 7
beats: "AI regulation; open-weight model releases; semiconductor supply"
orchestrator:
name: "Editor"
model: "gemini-3.8-flash"
instruction: |
<system_identity>
You are the Editor of a one-page morning briefing for one reader.
The current date is {{current_date}}; "today" means that date, and
"since yesterday" means the 24 hours before it.
</system_identity>
<method>
You MUST call 'Researcher' once per beat in this list before writing
a word: {{beats}}. Pass each beat with the date. Write only after
every report is in. If a beat came back EMPTY SWEEP, say so in the
last line; never fill it from memory.
</method>
<output_format>
1. THE LEAD: two to four sentences on what changed since yesterday,
plain prose, no preamble, no greeting.
2. THE ITEMS: one "- " line per item, at most {{headline_count}} in
total across all beats, ordered by importance, each ending with
its source domain in parentheses. An item reported by one outlet
carries the word "unverified" before the source.
3. One final line naming any beat whose sweep was empty, or
"All beats reported." if none was.
No headers, no bold, no closing summary. Under 1,500 characters.
</output_format>
generateContentConfig:
maxOutputTokens: 4096
thinkingConfig:
thinkingLevel: MEDIUM
includeThoughts: false
subagents:
- name: "Researcher"
description: "Searches the web for what was published in the last 24 hours on ONE beat and returns items with dates and source domains. Pass it one beat and the date line."
model: "gemini-3.1-flash-lite"
tools:
- "web_search"
instruction: |
You are the Researcher. You receive one beat and the current date.
Use web_search to find what was published in the last 24 hours on
that beat; prefer wire services and primary sources. Return a plain
list, one item per line: the publication date, the source domain,
and one sentence. Mark an item reported by one outlet only as
single-source. If nothing was published, return EMPTY SWEEP and
nothing else. Never add analysis. IMPORTANT: after receiving tool
results you MUST return a final text summary to the orchestrator.
generateContentConfig:
maxOutputTokens: 2048
This configuration file exists to prevent the Editor from writing before the reports are in, prevent it from filling a quiet beat from memory, and allow you to audit the finished page without reading the news. The variables block sets an upper headline limit and defines the beats as a semicolon-delimited string. In the system identity block, the prompt anchors the temporal window by binding “today” and “since yesterday” to the date line injected by the runtime. The method block enforces the execution sequence: the Editor must dispatch one call to the Researcher for each beat, collect every report before drafting, and report an empty sweep on the final line whenever a sweep returns no articles. The output contract establishes countable rules for the layout: a lead between two and four sentences, at most seven bullet items terminating in parenthetical source domains, an unverified tag on single-source claims, a definitive status line reporting sweep coverage, and a ceiling of fifteen hundred characters. At the bottom of the file, the Researcher prompt ends with an explicit instruction to return text because the subagent loop otherwise halts after the tool call with no text returned.
The output contract is written as countable rules. You can evaluate the sentence count of the lead, the number of listed items, the terminating domain on each line, the presence of the unverified indicator, and the explicit notation of an empty sweep. You can verify every constraint programmatically or by eye without reading the underlying news.
step three: run it once by hand, into a file
mkdir -p briefings
npm run chat:syndicate -- --syndicate briefing "Today's briefing." > "briefings/$(date +%F).md"
The --syndicate flag identifies your configuration file, the string argument provides the one-shot prompt, and the shell redirects standard output to a dated markdown file. Open the resulting artifact and verify the structural criteria: the sentence count in the lead, the total item count, the parenthetical source domain concluding every bullet, and the closing status line. Any variance indicates an instruction constraint that failed under probability sampling. When an execution fails its contract, we can isolate five architectural levers: the underlying model, the system instruction, the tool definitions, the inference parameters, or the user input. Here, we hold four levers constant and refine the instruction, adjusting the output contract by adding precise constraints.
Next, override a single variable at the command line to verify dynamic binding:
npm run chat:syndicate -- --syndicate briefing --bind headline_count=3 "Today's briefing." > briefings/test-3.md
This invocation executes the same agent specification while overriding headline_count at load time, preserving the defined beats while reducing the item budget. This variable binding mechanism allows you to parameterize runs without editing the underlying configuration file.
step four: schedule it
A system scheduler requires commands that complete their execution lifecycle and exit cleanly. One-shot execution satisfies this requirement. On macOS or Linux, open your crontab configuration with crontab -e and add a scheduled task using the absolute path to your repository:
0 7 * * * cd /absolute/path/to/melchizedek-agents && /usr/local/bin/npm run chat:syndicate -- --syndicate briefing "Today's briefing." >> "$HOME/briefings/$(date +\%F).md" 2>&1
At 07:00 daily, cron navigates into the repository root, executes the one-shot syndicate command, and appends standard output and standard error to a dated markdown file. Two implementation details reflect standard cron behavior. First, cron initializes with a minimal environment, requiring an absolute path to the npm binary, which you can resolve with which npm. Second, the cron parser treats percent symbols as line breaks, requiring an escaped \%F date specifier. Navigating into the repository directory ensures the application runtime resolves the local .env file containing your API credentials. On macOS, launchd provides the native service management framework and executes the identical command line, though cron remains a compact and functional scheduling utility across POSIX systems.
step five: review two mornings side by side
After collecting outputs across two consecutive mornings, open both files and evaluate each against the output contract’s rules:
| Rule | Day one | Day two |
|---|---|---|
| lead is two to four sentences | ||
item count at or under headline_count | ||
| every item ends with a source domain | ||
| single-source items carry “unverified” | ||
| the last line names the empty beats, or says all reported | ||
| under 1,500 characters |
This table tests six rules across independent generation runs. A constraint satisfied across both executions demonstrates operational reliability. A constraint that fails on a single run indicates ambiguity in prompt instructions, requiring tighter schema specifications in config/agents/briefing.yaml rather than modifications to the shell pipeline. Next, compare the two lead paragraphs against each other. If an agent repeats identical claims across consecutive days under different citations, the underlying beat has produced no new developments, and the subagent prompt should report that quiet state explicitly.
Two engineering constraints govern this implementation. First, web search executes on the provider’s infrastructure, returning truncated text snippets to the model context. When a beat demands full document evaluation, append web_extract to the subagent tool list to implement a search-then-read pipeline that discovers URLs before scraping page content. Second, the temporal anchor is bound once during process startup. An execution running across midnight retains the start date in its context, so we schedule morning sweeps safely after the date transition.
equip yourself with the materials
- The briefing file, the cron line, the launchd note, and the review sheet: daily-briefing-kit.md.
- The council it started from, verbatim: global-synthesis-council.md.
- The modules: variables and delegation, registers as rules, sources and silence.
apply it to your own ecosystem
Broad public news provides a simple reference case, but targeted beats deliver direct organizational utility. An engineering team can configure its morning digest to track competitor release logs, dependency status pages, overnight issue triage, and specific regulatory filings. Upstream repository releases form a critical monitoring target: package deprecations, breaking changes, and security advisories appear in version tags, where automated morning reviews prevent unexpected build failures. Adapt the briefing architecture to your environment:
- Define your beats within the
beatsvariable, and grant the researcher theweb_extracttool for any target requiring complete document extraction instead of query snippets. - Maintain the contract structure exactly, adjusting only the headline count and the maximum character limit.
- Execute the pipeline manually across three test runs, auditing each output against the six rules of the output contract.
- Automate execution via cron, directing the resulting artifact to an accessible storage path or exposing it through the endpoint project’s HTTP route.
After two weeks of scheduled execution, audit your team’s verification logs. Count the instances where engineers verified cited assertions against primary sources. An operational digest that prompts source verification grounds technical decisions in real-world evidence. An unverified digest that is accepted purely for its fluency risks introducing ungrounded completions into production planning.