Daily briefing generator. Aggregates signals from across the system into concise morning and evening briefings. Surfaces outcomes, opportunities, and decisio...
---
name: ocas-vesper
source: https://github.com/indigokarasu/vesper
install: openclaw skill install https://github.com/indigokarasu/vesper
description: Use when generating morning or evening briefings, requesting an on-demand system brief, checking pending decisions, or configuring the briefing schedule. Aggregates signals from across the system into concise natural-language summaries. Trigger phrases: 'morning briefing', 'evening briefing', 'what's happening', 'daily brief', 'pending decisions', 'catch me up', 'update vesper'. Do not use for deep research (use Sift), pattern analysis (use Corvus), or message drafting (use Dispatch).
metadata: {"openclaw":{"emoji":"🌅"}}
---
# Vesper
Vesper is the system's daily voice — it aggregates signals from every other skill and presents what matters as a concise, conversational morning or evening briefing, surfacing concrete outcomes, upcoming decisions, and actionable opportunities without exposing any internal architecture or analysis processes. Its signal filtering is strict: routine background activity, speculative observations, and already-experienced events are excluded, so every briefing earns attention rather than demanding it.
## When to use
- Generate morning or evening briefing
- Request an on-demand briefing
- Check pending decision requests
- Configure briefing schedule or sections
## When not to use
- Deep research — use Sift
- Pattern analysis — use Corvus
- Message drafting — use Dispatch
- Action execution — use relevant domain skill
## Responsibility boundary
Vesper owns briefing generation, signal aggregation, and decision presentation.
Vesper does not own: pattern analysis (Corvus), web research (Sift), communications delivery (Dispatch), action decisions (Praxis).
Vesper receives InsightProposal files from Corvus. Vesper writes completed briefings to its `briefings/` directory; Dispatch picks them up and delivers them.
## Ontology types
Vesper aggregates signals and data from other skills for briefing generation. During aggregation, it observes entities that appear in briefing content:
- **Entity/Person** — people mentioned in briefings (from calendar events, messages, task assignments)
- **Concept/Event** — events and deadlines referenced in briefing sections (meetings, due dates, travel departures)
- **Place** — locations mentioned in briefing content (meeting venues, travel destinations, weather locations)
Vesper may reference entity names and types from Chronicle or other skill data in briefing content (read-only). Entity observations are recorded in journal outputs for downstream Chronicle ingestion.
## Commands
- `vesper.briefing.morning` — generate morning briefing
- `vesper.briefing.evening` — generate evening briefing
- `vesper.briefing.manual` — on-demand briefing
- `vesper.decisions.pending` — list unacted decision requests
- `vesper.config.set` — update schedule, sections, delivery
- `vesper.status` — last briefing time, pending decisions, schedule
- `vesper.journal` — write journal for the current run; called at end of every run
- `vesper.update` — pull latest from GitHub source; preserves journals and data
## Invocation modes
- **Automatic morning** — during configured morning window
- **Automatic evening** — during configured evening window
- **Manual** — on user request
## Signal filtering rules
Include: actionable information, meaningful outcomes, plan-affecting changes, multi-signal opportunities, preparation-useful information.
Exclude: routine background activity, already-experienced events, internal system reasoning, speculative observations.
Evening-specific: no past weather, no summaries of attended meetings.
Read `references/signal_filtering.md` for full rules.
## Formatting rules
- Output is plain text or minimal HTML suitable for Gmail rendering. No markdown syntax (#, **, ---).
- Conversational paragraphs, not bullet dumps.
- Section headers use monochrome extended characters: ▪ Today, ✉ Messages, ⚑ Logistics, ◈ Markets, ⟡ Decisions, ⚙ System.
- Sections with no content are omitted entirely. Do not render empty sections or "nothing to report" placeholders.
- Normal-state system health is silence, not confirmation. No "no flags", "systems normal", "all clear".
- Opening: "Good morning Jared" (no punctuation after greeting). Evening: "Good evening Jared".
- Weather follows greeting as narrative prose with emoji directly before each condition word. No location callout when at home. When traveling, prefix with location: "Here's what Tokyo looks like today."
- Weather includes: current temp and condition, 10am commute forecast, high, 4pm commute forecast, low. Friday briefings append a weekend forecast line.
- Links are inline: the relevant words become the anchor text. No trailing link labels. Calendar events link to gcal, locations link to Google Maps, message references link to Gmail threads, tracking items link to status pages.
- URI formats: gcal `https://calendar.google.com/calendar/event?eid={event_id}`, maps `https://maps.google.com/?q={place+name+address}`, gmail `https://mail.google.com/mail/u/0/#inbox/{thread_id}`.
- Markets (morning): "Portfolio closed yesterday at $XXX,XXX (±X.X%)". Markets (evening): "Portfolio opened at $XXX,XXX and closed at $XXX,XXX (±X.X%)". Notable movers only when movement is material.
- Decision requests: option, benefit, cost, framed as optional.
- Opportunities surfaced without exposing underlying analysis.
- When Vibes (ocas-vibes) is present, apply its voice and anti-AI rules to all briefing text.
Read `references/briefing_templates.md` for structure and examples.
## Run completion
After every briefing generation:
1. Read InsightProposal files from each skill's `proposals/` directory: `/workspace/openclaw/data/ocas-corvus/proposals/` and `/workspace/openclaw/data/ocas-custodian/proposals/`. Apply signal filtering to each. Track consumed `proposal_id` values in `signals_evaluated.jsonl` to avoid reprocessing on future runs.
2. Read Dispatch summary from `/workspace/openclaw/data/ocas-dispatch/reports/YYYY-MM-DD-{period}.json` if present (where `period` matches the briefing type: `morning` or `evening`). Use `high_priority_threads`, `pending_followups`, and `active_commitments` for the Messages section.
3. Read Rally daily report from `/workspace/openclaw/data/ocas-rally/reports/YYYY-MM-DD-daily.json` if present. Use for the Markets section.
4. Write briefing file to `/workspace/openclaw/data/ocas-vesper/briefings/YYYY-WXX/YYYY-MM-DD-{type}.json` using `VesperBriefingFile` schema. This is Dispatch's pickup source. Week directory format: ISO week e.g. `2026-W14`. Create the week directory if absent.
5. Persist briefing record and evaluated signals to local JSONL files
6. Log material decisions to `decisions.jsonl`
7. Write journal via `vesper.journal`
## Behavior constraints
- No nagging — ignored decisions are treated as intentional
- No internal system terminology
- No references to architecture or analysis processes
- No speculative observations
- Only concrete outcomes and actionable opportunities
- Silence on normal — if a system, section, or status has nothing noteworthy, omit it entirely rather than confirming normalcy
## Inter-skill interfaces
**Corvus → Vesper (cooperative read):** Corvus writes InsightProposal files to `/workspace/openclaw/data/ocas-corvus/proposals/{proposal_id}.json`. Vesper reads from this directory during briefing generation, applies signal filtering, and tracks consumed `proposal_id` values in its own `signals_evaluated.jsonl`. Corvus does not write to Vesper's directories. See `spec-ocas-interfaces.md` for the InsightProposal schema.
**Custodian → Vesper (cooperative read):** Custodian writes InsightProposal files (`anomaly_alert` type) to `/workspace/openclaw/data/ocas-custodian/proposals/{proposal_id}.json` on Tier 3/4 escalations. Vesper reads from this directory during briefing generation. Custodian does not write to Vesper's directories.
**Dispatch → Vesper (cooperative read):** Dispatch writes `DispatchSummaryReport` to `/workspace/openclaw/data/ocas-dispatch/reports/YYYY-MM-DD-{period}.json`. Vesper reads this during briefing generation. Dispatch does not write to Vesper's directories.
**Rally → Vesper (cooperative read):** Rally writes daily portfolio reports to `/workspace/openclaw/data/ocas-rally/reports/YYYY-MM-DD-daily.json`. Vesper reads this during briefing generation. Rally does not write to Vesper's directories.
**Vesper → Dispatch (cooperative read):** Vesper writes completed briefings to `/workspace/openclaw/data/ocas-vesper/briefings/YYYY-WXX/YYYY-MM-DD-{type}.json`. Dispatch reads this directory, identifies undelivered briefings, and delivers them. See `references/schemas.md` VesperBriefingFile.
## Storage layout
```
/workspace/openclaw/data/ocas-vesper/
config.json
briefings.jsonl
signals_evaluated.jsonl
decisions_presented.jsonl
decisions.jsonl
briefings/
YYYY-WXX/
YYYY-MM-DD-morning.json
YYYY-MM-DD-evening.json
/workspace/openclaw/journals/ocas-vesper/
YYYY-MM-DD/
{run_id}.json
```
Default config.json:
```json
{
"skill_id": "ocas-vesper",
"skill_version": "2.7.0",
"config_version": "1",
"created_at": "",
"updated_at": "",
"schedule": {
"morning_window": "07:00-09:00",
"evening_window": "17:00-19:00",
"timezone": "America/Los_Angeles"
},
"sections": {
"today": true,
"messages": true,
"logistics": true,
"markets": true,
"decisions": true,
"system": true
},
"retention": {
"days": 30,
"max_records": 10000
}
}
```
## OKRs
Universal OKRs from spec-ocas-journal.md apply to all runs.
```yaml
skill_okrs:
- name: signal_precision
metric: fraction of included signals rated actionable by user
direction: maximize
target: 0.85
evaluation_window: 30_runs
- name: terminology_compliance
metric: fraction of briefings free of internal system terminology
direction: maximize
target: 1.0
evaluation_window: 30_runs
- name: decision_framing
metric: fraction of decision requests including option, benefit, and cost
direction: maximize
target: 1.0
evaluation_window: 30_runs
```
## Optional skill cooperation
- Vibes — reads voice identity, channel rules, and anti-AI pattern references from ocas-vibes before generating briefing text. If Vibes is absent, Vesper generates without voice guidance.
- Corvus — reads InsightProposal files from Corvus's `proposals/` directory (cooperative read; Corvus owns its output)
- Custodian — reads InsightProposal files from Custodian's `proposals/` directory (cooperative read; Custodian owns its output)
- Dispatch — reads `DispatchSummaryReport` from `/workspace/openclaw/data/ocas-dispatch/reports/YYYY-MM-DD-{period}.json` for the Messages section (cooperative read; Dispatch owns its data). Dispatch picks up completed briefings from Vesper's `briefings/` directory for delivery.
- Rally — reads portfolio daily reports at `/workspace/openclaw/data/ocas-rally/reports/YYYY-MM-DD-daily.json` (cooperative read; Rally owns its data).
- Calendar/Weather — reads external context for briefing content
- Elephas — journal entity observations consumed during Chronicle ingestion
## Journal outputs
Action Journal — every briefing generation run.
When entities are encountered during a run, include structured entity observations in `decision.payload`:
- `entities_observed` — list of entities encountered (Entity/Person, Concept/Event, Place), each with type, name, and context
- `relationships_observed` — connections between entities (e.g., a person attending a meeting, an event at a location)
- `preferences_observed` — user preferences inferred from briefing interactions (e.g., sections the user engages with, decisions acted upon)
Each entity observation must include a `user_relevance` field:
- `user` — entity is directly related to the user's world (people from the user's calendar/tasks, the user's deadlines, the user's meeting locations). Most entities from the user's own calendar, task list, and messages are `user`-relevant.
- `agent_only` — entity encountered incidentally from external context (e.g., a public figure mentioned in a news item, a location from a weather feed, entities from aggregated external sources rather than the user's personal data)
- `unknown` — relevance is unclear
## Initialization
On first invocation of any Vesper command, run `vesper.init`:
1. Create `/workspace/openclaw/data/ocas-vesper/` and subdirectories (`briefings/`)
2. Write default `config.json` with ConfigBase fields if absent
3. Create empty JSONL files: `briefings.jsonl`, `signals_evaluated.jsonl`, `decisions_presented.jsonl`, `decisions.jsonl`
4. Create `/workspace/openclaw/journals/ocas-vesper/`
5. Register cron jobs `vesper:morning`, `vesper:evening`, and `vesper:update` if not already present (check `openclaw cron list` first)
6. Log initialization as a DecisionRecord in `decisions.jsonl`
## Background tasks
| Job name | Mechanism | Schedule | Command |
|---|---|---|---|
| `vesper:morning` | cron | `0 6 * * *` (daily 6am) | `vesper.briefing.morning` |
| `vesper:evening` | cron | `0 20 * * *` (daily 8pm) | `vesper.briefing.evening` |
| `vesper:update` | cron | `0 0 * * *` (midnight daily) | `vesper.update` |
Cron options: `sessionTarget: isolated`, `lightContext: true`, `wakeMode: next-heartbeat`.
Default times are 6am and 8pm PT. Override with `vesper.config.set morning_hour <H>` and `vesper.config.set evening_hour <H>`.
Registration during `vesper.init`:
```
openclaw cron list
# If vesper:morning absent:
openclaw cron add --name vesper:morning --schedule "0 6 * * *" --command "vesper.briefing.morning" --sessionTarget isolated --lightContext true --wakeMode next-heartbeat --timezone America/Los_Angeles
# If vesper:evening absent:
openclaw cron add --name vesper:evening --schedule "0 20 * * *" --command "vesper.briefing.evening" --sessionTarget isolated --lightContext true --wakeMode next-heartbeat --timezone America/Los_Angeles
# If vesper:update absent:
openclaw cron add --name vesper:update --schedule "0 0 * * *" --command "vesper.update" --sessionTarget isolated --lightContext true --timezone America/Los_Angeles
```
## Self-update
`vesper.update` pulls the latest package from the `source:` URL in this file's frontmatter. Runs silently — no output unless the version changed or an error occurred.
1. Read `source:` from frontmatter → extract `{owner}/{repo}` from URL
2. Read local version from `skill.json`
3. Fetch remote version: `gh api "repos/{owner}/{repo}/contents/skill.json" --jq '.content' | base64 -d | python3 -c "import sys,json;print(json.load(sys.stdin)['version'])"`
4. If remote version equals local version → stop silently
5. Download and install:
```bash
TMPDIR=$(mktemp -d)
gh api "repos/{owner}/{repo}/tarball/main" > "$TMPDIR/archive.tar.gz"
mkdir "$TMPDIR/extracted"
tar xzf "$TMPDIR/archive.tar.gz" -C "$TMPDIR/extracted" --strip-components=1
cp -R "$TMPDIR/extracted/"* ./
rm -rf "$TMPDIR"
```
6. On failure → retry once. If second attempt fails, report the error and stop.
7. Output exactly: `I updated Vesper from version {old} to {new}`
## Visibility
public
## Support file map
| File | When to read |
|---|---|
| `references/schemas.md` | Before creating briefings, sections, or decision requests |
| `references/briefing_templates.md` | Before generating briefing content |
| `references/signal_filtering.md` | Before evaluating signals for inclusion |
| `references/journal.md` | Before vesper.journal; at end of every run |
don't have the plugin yet? install it then click "run inline in claude" again.
added explicit inputs section with external connections and config paths, expanded procedure into numbered steps with input/output per step, extracted decision logic into dedicated decision points section with 13 branches, detailed output contract with JSON schemas and file locations, and outcome signals for each command type including failure modes and timeouts.
vesper is the system's daily voice, aggregating signals from every other skill into concise, conversational morning or evening briefings. it surfaces concrete outcomes, upcoming decisions, and actionable opportunities without exposing internal architecture or analysis processes. use vesper when you need a morning or evening briefing, want to check pending decisions, request an on-demand briefing, or configure the briefing schedule. do not use vesper for deep research (use Sift), pattern analysis (use Corvus), or message drafting (use Dispatch).
External connections:
/workspace/openclaw/data/ocas-corvus/proposals/ (cooperative read; Corvus owns output)/workspace/openclaw/data/ocas-custodian/proposals/ for Tier 3/4 escalations (cooperative read)/workspace/openclaw/data/ocas-dispatch/reports/YYYY-MM-DD-{period}.json for Messages section (cooperative read; Dispatch owns data)/workspace/openclaw/data/ocas-rally/reports/YYYY-MM-DD-daily.json for Markets section (cooperative read)Configuration:
/workspace/openclaw/data/ocas-vesper/config.json with keys: schedule.morning_window (default "07:00-09:00"), schedule.evening_window (default "17:00-19:00"), schedule.timezone (default "America/Los_Angeles"), sections flags (all default true)vesper.config.set morning_hour <H> or vesper.config.set evening_hour <H>Invocation modes:
vesper.briefing.manualContext available at runtime:
signals_evaluated.jsonl to prevent reprocessing)Morning briefing generation (vesper.briefing.morning):
check if morning window is active (current time within schedule.morning_window). if outside window, log warning and stop. if forced (manual invocation), proceed.
read all InsightProposal files from /workspace/openclaw/data/ocas-corvus/proposals/ and /workspace/openclaw/data/ocas-custodian/proposals/. for each proposal, check proposal_id against signals_evaluated.jsonl. skip proposals already processed.
apply signal filtering rules: include actionable information, meaningful outcomes, plan-affecting changes, multi-signal opportunities, preparation-useful information. exclude routine background activity, already-experienced events, internal system reasoning, speculative observations. mark each proposal as "included" or "filtered" with reason. append consumed proposal_id values to signals_evaluated.jsonl.
fetch Google Calendar data for today and next 7 days (for Logistics section). fetch weather: current condition, 10am commute forecast, high, 4pm commute forecast, low. if friday, append weekend forecast line. apply location context: if user is traveling (detected from calendar itinerary or explicit location setting), prefix weather with location name ("here's what tokyo looks like today"); if at home, omit location callout.
read Dispatch summary from /workspace/openclaw/data/ocas-dispatch/reports/YYYY-MM-DD-morning.json if present. extract high_priority_threads, pending_followups, active_commitments for Messages section.
read Rally daily report from /workspace/openclaw/data/ocas-rally/reports/YYYY-MM-DD-daily.json if present. extract portfolio data for Markets section. format: "portfolio closed yesterday at $XXX,XXX (±X.X%)". surface notable movers only when movement is material (>2%).
build briefing sections in order: greeting (no punctuation: "good morning jared"), weather, Today (calendar events, deadlines), Messages (from Dispatch), Logistics (travel, location context), Markets (portfolio, notable movers), Decisions (unacted decision requests), System (anomalies from Custodian proposals). omit any section with no content (do not render empty sections or "nothing to report" placeholders).
apply formatting rules: plain text or minimal HTML suitable for Gmail. conversational paragraphs, not bullet dumps. section headers use monochrome extended characters (▪ Today, ✉ Messages, ⚑ Logistics, ◈ Markets, ⟡ Decisions, ⚙ System). links are inline (relevant words become anchor text; no trailing link labels). calendar events link to https://calendar.google.com/calendar/event?eid={event_id}, locations link to https://maps.google.com/?q={place+name+address}, message references link to https://mail.google.com/mail/u/0/#inbox/{thread_id}, tracking items link to status pages. normal-state system health is silence, not confirmation (no "no flags", "systems normal", "all clear").
if Vibes is present, apply voice guidance and anti-AI pattern rules to all briefing text. otherwise generate without voice guidance.
write briefing file to /workspace/openclaw/data/ocas-vesper/briefings/YYYY-WXX/YYYY-MM-DD-morning.json using VesperBriefingFile schema (see references/schemas.md). create week directory (ISO week format e.g. 2026-W14) if absent. include content, sections_rendered, signals_included, decisions_presented, entities_observed, timestamp, delivery_status.
append briefing record to /workspace/openclaw/data/ocas-vesper/briefings.jsonl with keys: date, type (morning/evening), sections_rendered, signal_count, decision_count, timestamp.
append evaluated signals to /workspace/openclaw/data/ocas-vesper/signals_evaluated.jsonl with keys: proposal_id, skill, decision, reason, timestamp.
for each decision request included in briefing, append to /workspace/openclaw/data/ocas-vesper/decisions_presented.jsonl with keys: decision_id, option, benefit, cost, presented_at, presented_in (morning/evening).
call vesper.journal to write Action Journal entry with entity observations (see journal outputs section).
log completion and return briefing file path to invoker.
Evening briefing generation (vesper.briefing.evening):
1-14. repeat morning procedure with these modifications:
schedule.evening_window for window check (default "17:00-19:00")/workspace/openclaw/data/ocas-dispatch/reports/YYYY-MM-DD-evening.json/workspace/openclaw/data/ocas-vesper/briefings/YYYY-WXX/YYYY-MM-DD-evening.json/workspace/openclaw/data/ocas-vesper/briefings.jsonl with type "evening"On-demand briefing (vesper.briefing.manual):
type parameter (default "manual"). skip window check./workspace/openclaw/data/ocas-vesper/briefings/YYYY-WXX/YYYY-MM-DD-manual.json and record type as "manual" in JSONL files.Check pending decisions (vesper.decisions.pending):
/workspace/openclaw/data/ocas-vesper/decisions_presented.jsonl.presented_at in last 72 hours and no corresponding entry in decisions.jsonl with matching decision_id and status "acted".decision_id, option, benefit, cost, presented_at, days_pending.Update configuration (vesper.config.set <key> <value>):
/workspace/openclaw/data/ocas-vesper/config.json.morning_hour, evening_hour, timezone, sections.{section_name}, retention.days, retention.max_records).config.json, set updated_at timestamp, write back to disk.openclaw cron update for affected jobs.Show status (vesper.status):
/workspace/openclaw/data/ocas-vesper/config.json and briefings.jsonl.timestamp and sections_rendered.vesper.decisions.pending to get unacted count.last_briefing (timestamp, type, section_count), pending_decisions (count, list of decision_id), schedule (morning_window, evening_window, timezone).Write journal (vesper.journal):
user_relevance: "user" (directly related to user's calendar, tasks, messages), "agent_only" (incidental context from external sources), "unknown" (unclear).skill_id (ocas-vesper), run_type (morning/evening/manual), entities_observed (list with type, name, context, user_relevance), relationships_observed (e.g., person attending event, event at location), preferences_observed (sections engaged, decisions acted), signals_evaluated_count, decisions_presented_count, okr_metrics (signal_precision, terminology_compliance, decision_framing scores)./workspace/openclaw/journals/ocas-vesper/YYYY-MM-DD/{run_id}.json (run_id is UUID). create directory if absent.Self-update (vesper.update):
source: URL from skill frontmatter (should be https://github.com/indigokarasu/vesper).skill_version field).gh api "repos/indigokarasu/vesper/contents/skill.json" --jq '.content' | base64 -d | python3 -c "import sys,json;print(json.load(sys.stdin)['version'])". set timeout to 10 seconds.https://api.github.com/repos/indigokarasu/vesper/tarball/main, extract to temp directory, copy all files into skill directory, delete temp directory.Initialization (vesper.init):
/workspace/openclaw/data/ocas-vesper/ and subdirectories: briefings/.created_at, updated_at.briefings.jsonl, signals_evaluated.jsonl, decisions_presented.jsonl, decisions.jsonl./workspace/openclaw/journals/ocas-vesper/ directory.openclaw cron list. check for jobs named vesper:morning, vesper:evening, vesper:update. for each absent, invoke openclaw cron add with exact parameters (see background tasks table below).decisions.jsonl: {"decision_id":"vesper-init-{timestamp}","action":"initialized","timestamp":"{iso_timestamp}","status":"acted"}.window check for automatic briefing: if invocation is automatic (cron-triggered) and current time is outside configured window (morning_window or evening_window), log warning and stop. if invocation is manual (user-requested), skip window check and proceed.
proposal already processed:
if proposal_id exists in signals_evaluated.jsonl with decision set to "included" or "filtered", skip reading and evaluating the proposal again. this prevents double-counting signals across runs.
dispatch data present: if DispatchSummaryReport exists at the expected path and timestamp is recent (within last 24 hours), read high_priority_threads, pending_followups, active_commitments and populate Messages section. if file absent or stale (>24 hours old), omit Messages section entirely.
rally data present: if Rally daily report exists at the expected path and timestamp is recent (within last 24 hours), extract portfolio data and populate Markets section. if file absent or stale, omit Markets section.
weather location context: if user calendar contains travel events with location data (flight, hotel, arrival/departure times), user is traveling. detect destination location from event. prefix weather narrative with location ("here's what tokyo looks like today"). if no travel events detected, user is at home. omit location callout and render weather without location prefix.
evening weather rules: exclude any historical weather data (past conditions, past forecasts). exclude summaries of calendar events already completed. surface only forward-looking forecasts and preparation-useful context.
vibes present: if ocas-vibes skill is installed and accessible, read voice config and anti-AI pattern references. apply voice guidance (lowercase, punchy, direct cadence) and anti-AI rules to all briefing text (no corporate hedging, no sycophancy, no marketing hype). if Vibes absent, generate briefing text without voice guidance (use default neutral technical voice).
decision request framing:
for each decision request included in briefing, verify presence of option, benefit, and cost fields. frame decision as optional ("you could also consider X"). if any field missing, do not include decision in briefing (filter it out and note reason in signals_evaluated.jsonl).
empty sections: if a section has no content after filtering (e.g., no calendar events, no pending decisions), do not render that section header or placeholder text. omit the section entirely from briefing output.
normal system health: if system status is normal with no anomalies (no Custodian proposals, no Corvus alerts), do not include System section. silence on normal is the rule, not confirmation.
rate limit or timeout on external reads: if read from Corvus, Custodian, Dispatch, or Rally times out (>10 seconds) or returns 429 (rate limit), skip that data source. continue generating briefing with available data. log timeout event in signals_evaluated.jsonl with reason "timeout". do not fail the entire briefing.
config validation:
when user invokes vesper.config.set, validate the key exists in config schema. validate value type (hours 0-23 integer, timezone string from IANA, boolean for section flags, positive integer for retention). if validation fails, return error message and do not modify config.json.
cron schedule update:
if morning_hour or evening_hour config changes via vesper.config.set, recalculate cron schedule (e.g., morning_hour 6 = schedule "0 6 * * *"). invoke openclaw cron update --name vesper:morning --schedule "{new_schedule}" to propagate change.
Morning and evening briefings:
/workspace/openclaw/data/ocas-vesper/briefings/YYYY-WXX/YYYY-MM-DD-{type}.json where type is "morning", "evening", or "manual". week directory must exist (ISO week format 2026-W14).content (string, plain text or minimal HTML), sections_rendered (array of section names present), signals_included (count), decisions_presented (array of decision_id), entities_observed (array with type, name, context, user_relevance), timestamp (ISO 8601), delivery_status (string, "pending_delivery" on creation).JSONL files:
briefings.jsonl: append one record per briefing (morning, evening, manual). keys: date (YYYY-MM-DD), type, sections_rendered (array), signal_count, decision_count, timestamp.signals_evaluated.jsonl: append one record per evaluated proposal. keys: proposal_id, skill (source skill name), decision ("included" or "filtered"), reason (string), timestamp.decisions_presented.jsonl: append one record per decision included in briefing. keys: decision_id, option, benefit, cost, presented_at (ISO 8601), presented_in (morning/evening/manual).decisions.jsonl: append when user acts on a decision (inferred from subsequent briefings). keys: decision_id, action (text description), timestamp, status ("acted" or "ignored").config.json:
/workspace/openclaw/data/ocas-vesper/config.jsonskill_id, skill_version, config_version, created_at, updated_at, schedule (morning_window, evening_window, timezone), sections (flags for today, messages, logistics, markets, decisions, system), retention (days, max_records).Journal output:
/workspace/openclaw/journals/ocas-vesper/YYYY-MM-DD/{run_id}.json where run_id is UUID.skill_id, run_type, entities_observed (array with type, name, context, user_relevance), relationships_observed (array), preferences_observed (array), signals_evaluated_count, decisions_presented_count, okr_metrics (object with signal_precision, terminology_compliance, decision_framing float values 0-1).Command responses:
vesper.briefing.{morning|evening|manual}: return JSON {"briefing_path":"...", "timestamp":"...", "sections_count":N, "signal_count":N, "decision_count":N}.vesper.decisions.pending: return JSON {"pending_count":N, "decisions":[{"decision_id":"...","option":"...","benefit":"...","cost":"...","presented_at":"...","days_pending":N}]}.vesper.config.set: return JSON {"key":"...","value":"...","status":"updated"} or error JSON with status "error" and message.vesper.status: return JSON {"last_briefing":{"timestamp":"...","type":"...","section_count":N},"pending_decisions":{"count":N},"schedule":{"morning_window":"...","evening_window":"...","timezone":"..."}}.vesper.journal: return JSON {"journal_path":"...","timestamp":"...","entity_count":N}.vesper.update: return string "I updated Vesper from version X to Y" or "no update available" (silent; output only on change or error).briefing delivery success:
/workspace/openclaw/data/ocas-vesper/briefings/YYYY-WXX/YYYY-MM-DD-{type}.json within 5 seconds of command invocation.delivery_status is set to "pending_delivery".signal filtering success:
proposal_id entries marked "included" or "filtered" with reasons.decision presentation success:
configuration change success:
vesper.config.set key value returns status "updated".pending decision check success:
vesper.decisions.pending returns list of decision_id from last 72 hours not yet acted upon.journal write success:
/workspace/openclaw/journals/ocas-vesper/YYYY-MM-DD/{run_id}.json within 2 seconds of briefing completion.entities_observed array with at least one entity (or empty array if no entities encountered).self-update success:
vesper.update completes without error.failure modes (user sees):
vesper.init to register.