Daily briefing generator. Aggregates signals from across the system into
Skill name: ocas-vesper
Version: 2.13.0
Type:
Layer: productivity
Author:
Daily briefing generator. Aggregates signals from across the system into
vesper.briefing.morning— generate morning briefingvesper.briefing.evening— generate evening briefingvesper.briefing.manual— on-demand briefingvesper.briefing.deliver— deliver undelivered briefings via email usingmcp_google_workspace_send_gmail_message(NOTbriefing_deliver.pywhich is broken). Scan individual briefing files for non-delivered status, convert content to HTML, send via MCP, update both the individual file andbriefings.jsonlon success. In cron sessions where the email MCP is unavailable,python3 scripts/delivery_check.py --type <morning|evening|all> --deliverperforms the same scan, delivers via the Telegram fallback, and updates both records — see Direct Telegram delivery gotcha.vesper.briefing.check— inspect the latest briefing file (scripts/check_briefing.py)vesper.decisions.pending— list unacted decision requestsvesper.config.set— update schedule, sections, deliveryvesper.status— last briefing time, pending decisions, schedulevesper.journal— write journal for the current run; called at end of every runvesper.update— pull latest from GitHub source; preserves journals and dataentities_observed— type, name, contextrelationships_observed— connections between entitiespreferences_observed— user preferences inferred from briefing interactionsbriefing_deliver.pyis BROKEN — use MCP tools directly — Thescripts/briefing_deliver.pyscript usesgoogleapiclientdirectly and will fail with import/auth errors in cron mode. When delivering briefings, always usemcp_google_workspace_send_gmail_messagevia MCP. Build HTML manually from thecontentfield — no template file needed. See the delivery check procedure in cron mode for the correct approach.delivery_statushas THREE formats — The field can be: (1) a plain string:"delivered","pending", or"silent"; (2) an object with{status: "delivered", delivered_at: "..."}; (3) an object with{status: "failed", failed_at: "...", reason: "..."}. When scanning for undelivered briefings, check ALL three forms. A briefing is undelivered if:delivery_statusis"pending", ORdelivery_status.statusis"failed"or"pending", ORdeliveredisfalseornull/None. Skip"silent"status — these are intentionally suppressed briefings with no content, not failed deliveries. Also skip"draft"status in JSONL — this is an intermediate state that can get stuck; always cross-reference against the individual file'sdelivery_statusbefore treating a"draft"JSONL entry as undelivered.delivered: nullwithdelivery_status: "delivered"desync — A specific desync where the individual briefing file hasdelivered: null(JSON null, never set) butdelivery_status: "delivered"(plain string) with a validdelivered_attimestamp. This happens when delivery succeeds but the code path that sets the top-leveldeliveredboolean doesn't run. Thedelivery_statusfield is authoritative — if it says"delivered"with a timestamp, the briefing WAS delivered. Fix by settingdelivered: truein the individual file. Always check both fields independently.patchis unreliable afterread_file—read_filewraps output withN|line prefixes that don't exist in the actual file. If you read a file viaread_fileand then trypatch, the old_string may never match. Useterminal+ Python (json.load/json.dumpper line) orwrite_fileto rewrite. This applies to ALL files, not just JSONL. Thepatchtool operates on raw file content. If you include theN|prefix in both old_string and new_string, you can accidentally create duplicate prefixes (e.g.,27|27|). SAFE alternatives: (1) Useterminal+sed -i 'Ns/^N|N|/|/'for surgical single-line prefix fixes. (2) Useterminal+ Python (json.loadper line, modify,json.dumpper line) for structured edits. (3) Usewrite_fileto rewrite the entire file from a parsed representation. NEVER usepatchwith theN|prefix included in both old and new strings — the prefix duplicates and corrupts the line, making it unparseable.quality_check.py— The quality check script lives at~/.hermes/profiles/indigo/skills/ocas-vesper/scripts/quality_check.py(the profile skill directory). Run it as an absolute path — relativescripts/quality_check.pyonly works when CWD happens to be that directory. Call:python3 ~/.hermes/profiles/indigo/skills/ocas-vesper/scripts/quality_check.py <briefing.json>. It returnsPASSorFAIL — Quality check issues found:with the failing terms.quality_check.pyfield name mismatch — Thescripts/quality_check.pycheck_sections_have_contentandcheck_decisions_tracefunctions now accept both field names:summaryORtextfor content items,section_typeORidfor sections. The VesperBriefingFile schema usessummaryandsection_type. Always verify field names inreferences/schemas.mdwhen modifying the quality script.contentfield is REQUIRED in VesperBriefingFile — Thereferences/schemas.mdVesperBriefingFile schema listscontentas a string field, but it's easy to omit when building the sections array. Thequality_check.pycheck_greetingfunction readsbriefing['content']and will FAIL if the field is missing (it returns''and the greeting check fails with "expected 'Good morning ...'"). Always includecontent— it's the rendered plain-text version of the full briefing, with section markers and newlines. Build it as you build the sections array.
See SKILL.md for outputs, journals, and persistence rules.
| File | Purpose |
|---|---|
SKILL.md |
Skill definition |
references/ |
Supporting documentation |
scripts/ |
Helper scripts |
- [2.10.0] - 2026-04-26
- Added
- [2.9.0] - 2026-04-18
- Changed
- Added
- [2.8.4] - 2026-04-12
- Added
- [2026-04-04] Spec Compliance Update
Read SKILL.md for operational details, schemas, and validation rules.
Read references/ for detailed specifications and examples.
MIT License — see LICENSE for details.
