OR Key
drop another .md file to compare - side-by-side diff against morning-brief

morning-brief

Pulls your day together into one screen of what needs you.
description: "Triggers on prompt mention of 'morning-brief'."
personal 2 files 10 recent evals

What it does for you

Pulls your day together into one screen of what needs you.

What it produces

A recent result, so you can see the kind of work it returns.

loading…

How to get it

These run inside the Snappy workspace. Want this working in your business? I set skills like this up with you, in one focused week.

Work with me
For developers how this skill is built, graded, and how it runs

at a glance- the short version

actorSweep→krisp merge + ranking logic.
auditorDedup + staleness cross-check.
eval modeauto
categoryKnowledge
stages2
dependssweep, krisp

what's inside - the parts that make up a skill 2/4 present

A skill is just a few plain-text files. Only the main one is required. The rest are optional, added as the work needs them. This is what the skill is made of; how it runs is just below.

The skill
state/skills/morning-brief/SKILL.md present
the skill itself, in plain text
The main file. It says what the skill is and lays out the steps in plain English.
Code
state/lib/morning-brief.ts not present
code the skill can run
Optional. Many skills are just words and need no code at all.
Scripts
state/bin/morning-brief/ not present
helper scripts
Optional. Added when a skill has a few commands to run.
Loader
state/skills/morning-brief/AGENTS.md present
what the AI loads on the fly
Loaded automatically the moment this skill is needed. Kept short on purpose.

how it's graded - what counts as a good run 4 criteria · 2 deterministic · 2 judge

Each row is one thing a good run has to get right. deterministic means a quick check decides, pass or fail. judge means the AI reads the result and rates it. Grading each piece on its own (instead of one overall score) shows exactly where a run fell short, so the fix is obvious.

name
kind
check
brief_output_location
deterministic
The skill must write a markdown digest to 'state/log/briefs/<date>.md'.
sweep_freshness_enforced
deterministic
If 'state/log/sweep-snapshots/latest.json' is missing or older than 4 hours, the 'sweep' skill must be re-invoked and 'latest.json' updated from a new timestamped snapshot.
krisp_cache_handling
judge
The skill must correctly handle missing, stale (>8h), or fresh Krisp cache data according to the specification, including appropriate `krisp_status` and content surfacing.
item_ranking_and_count
judge
The skill must sort items by age descending, take the top 15, and mark items as 'awaiting_robert' based on the specified logic.

how it runs - the shared frame every skill uses 5/5 present

Every skill runs the same way. One part does the work, a separate part checks it, and a short loader hands the AI exactly what it needs for the job. Anything this skill doesn't use shows a one-line note saying why, on purpose, not by accident.

makes the work The worker
present
Sweep→krisp merge + ranking logic. the worker
Does the actual work. Whatever it produces is what gets checked next.
checks the work The reviewer
present
Dedup + staleness cross-check. the checker
A separate checker grades the work, so the part that made it can't approve its own work.
frame
learns Self-correction
present
fixes itself learns from gaps
When a run hits a gap, the skill gets edited on the spot [FIXED] or queued for a bigger rewrite [LOGGED], so it keeps getting better.
tidies up Background fixes
present
queued for rewrite runs in the background
Bigger fixes that can't be made on the spot get queued and rewritten in the background later.
remembers Run history
present
state/log/evals.ndjson unknown runs
Every run is written down here, so the next time this skill is used it already knows how the last runs went.
Critical rules the things this skill must not get wrong
  1. ALWAYS check sweep-snapshot freshness FIRST — if state/log/sweep-snapshots/latest.json is missing or >4h old, RE-INVOKE the sweep skill, do not show yesterday's inbox
  2. ALWAYS dedup across channels — keys are channel + ":" + id. Duplicate items collapse the eval to 0.0
  3. If Krisp cache is empty (refresh.sh didn't run today), skip the commitment cross-check and set primary_issue: "krisp-cache-missing" — do NOT block the brief
  4. An empty brief MUST set reason_empty — silent empty (zero items, no reason) scores 0.0 (primary_issue: "silent-empty")
  5. Rank by awaiting_robert = true (Robert is not the last sender AND no Krisp commitment promises future reply); top 15 by age desc

what it has learned - fixes written back in over time sample

When a run hits something this skill didn't handle, the fix gets written back into the skill so it doesn't happen again. FIXED means it was corrected on the spot. LOGGED means it's queued for a bigger rewrite. Either way, the skill gets a little better and never makes the same mistake twice.

  1. Loading feedback rows…

how the work flows- who makes it, who checks it

inputs sweepkrisp
actor Sweep→krisp merge + ranking logic.
1 generator
invoke
actor = Sweep→krisp merge + ranking logic.
skill is prose — follow steps 1-5 in `state/skills/morning-brief/SKILL.md
auditor Dedup + staleness cross-check.
2 data
eval log
`state/log/evals.ndjson` (skill: "morning-brief")

SKILL.md- the skill, written out in plain English

morning-brief

Scope-only chain: pull the latest sweep snapshot, merge with Krisp commitment cache, emit a ranked list of "things Robert hasn't closed that are older than X hours."

Steps

  1. Freshness check - sweep snapshot. Read

state/log/sweep-snapshots/latest.json. Compute sweep_age_hours = (now - file.mtime) / 3600. If the file is missing OR sweep_age_hours > 4, re-invoke the sweep skill and then cp <newest-timestamped-inbox.json> state/log/sweep-snapshots/latest.json before continuing. (Note: sweepAll() writes a timestamped file but does NOT update latest.json automatically - the cp is mandatory.)

  1. Freshness check - Krisp cache. Check whether

~/.claude/cache/krisp/action-items.json exists and krisp_age_hours = (now - file.mtime) / 3600.

  • Missing (ENOENT): set krisp_status = "missing", skip commitment

cross-check, continue to step 3.

  • Stale (krisp_age_hours > 8): set krisp_status = "stale", surface

existing action_items[] but note the age in the brief header.

  • Fresh (krisp_age_hours ≤ 8): set krisp_status = "ok", use

action_items[] normally.

  1. For each inbox item, mark awaiting_robert = true if Robert is not the

last sender AND (krisp_status = "missing" OR no Krisp commitment exists that already promises a reply by date > now).

  1. Sort by age descending. Take top 15.
  2. Emit markdown digest to state/log/briefs/<date>.md and return path.

Eval

Actor: the sweep→krisp merge + ranking logic. Auditor: dedup + staleness cross-check.

const unique_ids = new Set(items.map(i => i.channel + ":" + i.id));
const has_dupes = unique_ids.size < items.length;
const all_aged = items.every(i => i.age_hours > 0);
const sweep_stale = sweep_snapshot_age_hours > 4;   // tightened from 6h
const krisp_missing = krisp_status === "missing";
const krisp_stale   = krisp_status === "stale";     // mtime > 8h

score("morning-brief", run_id, {
  score:
    items.length === 0 && reason_empty ? 1.0 :
    items.length === 0 && !reason_empty ? 0.0 :
    has_dupes ? 0.0 :
    !all_aged ? 0.0 :
    sweep_stale ? 0.5 :
    krisp_stale ? 0.5 :
    krisp_missing ? 0.75 :   // degraded but not blocked
    1.0,
  item_count: items.length,
  unique_count: unique_ids.size,
  sweep_stale,
  krisp_status,
  reason_empty,
  primary_issue:
    items.length === 0 && !reason_empty ? "silent-empty" :
    has_dupes ? "duplicate-items" :
    !all_aged ? "zero-age-item" :
    sweep_stale ? "stale-sweep-snapshot" :
    krisp_stale ? "krisp-cache-stale" :
    krisp_missing ? "krisp-cache-missing" : null,
});

Catches: duplicates across channels (dedup regression), stale sweep data (>4h), zero-age items (timestamp bugs), silent empty, stale Krisp cache (>8h), missing Krisp cache (degraded path - brief still emits).

Gotchas

  • Sweep snapshot staleness (>4h). Re-invoke sweep skill and cp the

timestamped output to latest.json before continuing. Using a stale snapshot without re-invoking scores 0.5 (primary_issue: "stale-sweep-snapshot"). The cp is mandatory - sweepAll() does NOT update latest.json automatically.

  • Krisp cache missing. If ~/.claude/cache/krisp/action-items.json is

absent, skip commitment cross-check, set krisp_status = "missing", and continue. The brief still emits; score is 0.75, not a failure.

  • Krisp cache stale (mtime >8h). Surface existing action_items[] but

tag krisp_status = "stale". Score is 0.5 (primary_issue: "krisp-cache-stale"). Do not block the brief.

  • LinkedIn fetcher may emit ts: "Apr 11" (string, no year) - Date.parse

resolves to year 2001 (~200,000h age). Treat non-numeric ts as recent; do not block the score.

Graduation

Stays prose until the chain shape is stable. Sidecar lands when the ranking logic has been tuned twice.

Rubric

criteria:
  - name: brief_output_location
    kind: deterministic
    check: "The skill must write a markdown digest to 'state/log/briefs/<date>.md'."
  - name: sweep_freshness_enforced
    kind: deterministic
    check: "If 'state/log/sweep-snapshots/latest.json' is missing or older than 4 hours, the 'sweep' skill must be re-invoked and 'latest.json' updated from a new timestamped snapshot."
  - name: krisp_cache_handling
    kind: judge
    check: "The skill must correctly handle missing, stale (>8h), or fresh Krisp cache data according to the specification, including appropriate `krisp_status` and content surfacing."
  - name: item_ranking_and_count
    kind: judge
    check: "The skill must sort items by age descending, take the top 15, and mark items as 'awaiting_robert' based on the specified logic."

AGENTS.md- what the AI loads when this skill comes up

morning-brief - loader

Per-turn rules for the morning-brief skill. Full reference: state/skills/morning-brief/SKILL.md. Do not skip these.

Critical Rules

  • ALWAYS check sweep-snapshot freshness FIRST - if state/log/sweep-snapshots/latest.json is missing or >4h old, RE-INVOKE the sweep skill, do not show yesterday's inbox
  • ALWAYS dedup across channels - keys are channel + ":" + id. Duplicate items collapse the eval to 0.0
  • If Krisp cache is empty (refresh.sh didn't run today), skip the commitment cross-check and set primary_issue: "krisp-cache-missing" - do NOT block the brief
  • An empty brief MUST set reason_empty - silent empty (zero items, no reason) scores 0.0 (primary_issue: "silent-empty")
  • Rank by awaiting_robert = true (Robert is not the last sender AND no Krisp commitment promises future reply); top 15 by age desc

Commands

| ui dashboard | state/skills/morning-brief/resources/ui.openui | |invoke: skill is prose - follow steps 1-5 in state/skills/morning-brief/SKILL.md |sweep snapshot: state/log/sweep-snapshots/latest.json |krisp cache: ~/.claude/cache/krisp/action-items.json (written by state/bin/krisp/refresh.sh; sibling meetings.json also written). Commitments live in action_items[] - there is no commitments.json. |output: state/log/briefs/<date>.md |eval log: state/log/evals.ndjson (skill: "morning-brief")

OpenUI Resource

  • Skill-owned OpenUI Lang resource: state/skills/morning-brief/resources/ui.openui. Read it before rendering or editing this skill's generated component surface.
  • Treat this resource as a first-class artifact of the skill, not a generic chat response. Improve it when the skill's user-facing output needs to become richer.
  • System resources compose OpenUI primitives and inherit SnappyChat tokens. Use ui_contract: branded in SKILL.md only for deliberate platform or client visuals.

Known Pitfalls

  • Zero-age items signal a timestamp bug - primary_issue: "zero-age-item" blocks the score
  • Stale sweep snapshot scores 0.5 even if dedup passes (primary_issue: "stale-sweep-snapshot")
  • Krisp cache missing is NOT a failure - it's a documented degraded path with primary_issue: "krisp-cache-missing" and the brief still emits
  • Krisp cache stale (file exists and mtime >8h) is a separate degraded path: surface the existing commitments but tag primary_issue: "krisp-cache-stale" so the eval scores 0.5, not 1.0
  • sweepAll() writes a timestamped 2026-MM-DDTHH-MM-inbox.json but does NOT update latest.json. After re-invoking sweep, cp <newest>-inbox.json latest.json or the staleness check keeps reading the old file.
  • LinkedIn fetcher may emit ts: "Apr 11" (string, no year) - Date.parse resolves to year 2001, producing a ~200,000h age. Treat non-numeric ts as recent and note the quirk; do not block the score.
  • LinkedIn fetcher timeout is swallowed silently by sweepAll() - errors: {} even when the page-load timed out and 0 LinkedIn items were returned. Log "linkedin-timeout-swallowed" in the brief's degraded-paths list when LinkedIn item count is 0; do not infer "no DMs" from absence.
  • Skill router still resolves morning-brief and sweep to the legacy flat path state/skills/<name>.md. Real canonical is folder-shape: state/skills/<name>/SKILL.md (+ AGENTS.md loader). When the launcher says "load the skill page and follow its steps" and that path 404s, fall back to <name>/SKILL.md.

Self-Test

An agent reading this should correctly:

  1. [ ] Re-invoke sweep skill when the snapshot is older than 4 hours rather than reusing it?
  2. [ ] Set reason_empty when emitting a zero-item brief, to avoid silent-empty?
  3. [ ] Continue without commitment-check when the Krisp cache is missing, marking primary_issue accordingly?

Self-report

If this loader fell short, append a line:

echo "[$(date -u +%FT%TZ)] morning-brief: <what was missing>" >> state/log/loader-feedback.log

<!-- kernel-ok: historical attribution for the PID-loader pattern. -->

Self-correcting loader (PID feedback)

This is the rule that makes loaders learn. Ported from snappy-kernel, where it demonstrably worked: the snappy-dom-cartographer skill mapped Skool admin UI once, every later ship-agent inherited the full map for free.

Before you finish, do two things:

1. Fix gaps (P - proportional). MANDATORY. If this loader didn't cover your case - if you had to read another file, run an undocumented command, work around a wrong selector, or discover a quirk - you MUST attempt an Edit to this AGENTS.md before you log.

  • Default action: edit this file. Surgical. Add the missing line, table

row, or code example where it belongs. No restructuring.

  • Only LOGGED is allowed when: the fix needs >10 lines, spans multiple

files, or requires a structural rewrite. In that case the state/regen/drain.sh queue picks it up asynchronously.

  • "I didn't have time" / "it's minor" / "the next agent will figure it out"

are NOT valid reasons. Minor gaps compound - that's the whole point of this loop.

  • Concrete example of a valid P-fix: discover agent-browser screenshot

takes path positionally not as --output <path>. One line in a table. Edit it in. Do not just log it.

  • The goal: the next agent never has to leave the loader.

2. Log the result.

echo "[$(date -u +%FT%TZ)] morning-brief: <what was missing or fixed> [FIXED|LOGGED]" >> state/log/loader-feedback.log
  • FIXED = you patched this loader inline (P-fix).
  • LOGGED = too large for inline; the PostToolUse enqueue + Stop-hook drain

will rewrite the loader from scratch on next session-end.

Do not skip this. Every agent run must leave the system better than it found it. The loader is the setpoint; you are the sensor; the gap is the error signal; closing the gap is the correction.

api.ts- the code it can call

⚠ no api.ts - this skill has no typed action surface

scripts- helper scripts it can run

prose-only skill - 2 inline code blocks live in SKILL.md above (no state/bin/ sidecar yet).

how we check it- the checks, plus the last 10 runs

rubric auto non-empty-items + shape gate
recent mean 0.70 · 10 runs actor/auditor: unverifiable
deps sweep krisp
timestamp verb score primary_issue artifact
2026-04-27 20:46Z - 1.00 - -
2026-04-27 18:48Z - 1.00 - -
2026-04-27 12:38Z - 0.50 - -
2026-04-27 00:00Z - 1.00 - -
2026-04-26 04:39Z - 0.50 - -
2026-04-25 23:50Z - 0.50 - -
2026-04-25 22:40Z - 0.50 - -
2026-04-25 04:36Z - 0.50 - -
2026-04-25 04:11Z - 1.00 - -
2026-04-24 22:47Z - 0.50 - -