Daily Check-in
The check-in is vita's primary daily touchpoint: a single conversation that gathers subjective health data, reports the day's equilibrium and any active gates, follows up on commitments and scheduling drift, confirms auto-detected activities, and occasionally deepens identity knowledge. Its depth adapts to how long it's been since the last check-in.
This page documents the /checkin command flow. It is distinct from the read-only Check-ins view in the web UI (covered at the end), which charts felt-vs-measured wellness over the same data store.
Why This Exists
Health tracking fails when it becomes a chore. The check-in is built around a few principles:
- Adaptive depth β a quick check after a recent one (2-3 questions), a deeper conversation after a gap (10+). Completion beats perfection: a 2-minute check-in is better than skipping.
- Data-driven questions β instead of a fixed questionnaire, the check-in opens with an equilibrium snapshot and asks about the lowest-scoring life dimensions. The questions follow where the system is actually weak.
- Proactive accountability β scheduling drift, overdue commitments, and unconfirmed activities are surfaced first, before small talk.
- Pattern persistence β responses are stored so trends are chartable and downstream consumers (weekly summary, insights, briefing, coaching) can use them.
Source of Truth: the Skill
/checkin is a thin command delegate. The real protocol lives in a skill:
| File | Role |
|---|---|
.claude/commands/checkin.md |
Command entry point; a 5-line pointer to the skill. |
.claude/skills/checkin/SKILL.md |
The full 10-step protocol β the source of truth. |
.claude/skills/checkin/references/adaptive-depth.md |
Depth-determination logic. |
.claude/skills/checkin/references/question-sources.md |
Question bank and prioritization. |
.claude/skills/checkin/references/commitment-capture.md |
Commitment detection + date parsing. |
.claude/skills/checkin/references/garmin-confirmations.md |
Activity-confirmation workflow. |
Note: There is no SessionStart shell hook for check-ins. The "prompt a check-in at session start if none today" behavior is a behavioral instruction in
CLAUDE.md, executed by the agent β not a configured hook in.claude/settings.json. The other live trigger is proactive outreach: the Telegram bot can prompt a morning/evening check-in (see Proactive Outreach).
Process Flow
/checkin
|
v
+--------------------------------------------------+
| 1. Equilibrium snapshot + sleep-gate check |
| compute_equilibrium() -> composite, gaps, |
| active_gates, layer_health |
+--------------------------------------------------+
|
v
+--------------------------------------------------+
| 1b. Sleep Guardian morning retrospective |
| get_morning_retrospective() (one line) |
+--------------------------------------------------+
|
v
+--------------------------------------------------+
| 2. Determine depth (gap since last check-in) |
| Quick <12h / Standard 12-48h / Deep >48h |
+--------------------------------------------------+
|
v
+--------------------------------------------------+
| 3. Gather pending items (graceful degradation) |
| drift / commitments / garmin / objectives |
+--------------------------------------------------+
|
v
+--------------------------------------------------+
| 4. Generate questions (priority order) |
| 5. Ask (batch if quick, interactive otherwise) |
+--------------------------------------------------+
|
v
+--------------------------------------------------+
| 6. Capture new commitments from the conversation |
+--------------------------------------------------+
|
v
+--------------------------------------------------+
| 7. Save data/checkins/YYYY-MM-DD.json |
| 8. Update data/scratch/daily.md |
+--------------------------------------------------+
|
v
+--------------------------------------------------+
| 9. Weekly leadership/role-transition check |
| 10. Optional identity question (~20%) |
+--------------------------------------------------+
Step-by-Step Implementation
Step 1: Equilibrium snapshot + sleep gate
Every check-in opens here. The equilibrium engine (scripts/executive/equilibrium.py) is pure Python β it reads existing data files and computes a 0-100 score for each life dimension, with no LLM or network calls.
from scripts.executive.equilibrium import compute_equilibrium
eq = compute_equilibrium()
print(f"eq {eq.composite_score:.0f}/100")
for gate in eq.active_gates:
print(f" gate: {gate['label']} β {gate['message']}")
for layer_id, lh in eq.layer_health.items():
if lh["status"] in ("stressed", "critical"):
print(f" {layer_id}: {lh['status']} (min={lh['min']})")
compute_equilibrium() returns an EquilibriumSnapshot with:
| Field | Meaning |
|---|---|
composite_score |
Weighted average across dimensions (0-100). |
mode |
maintenance / crunch / recovery / vacation β re-weights dimensions. |
dimensions |
Per-dimension DimensionScore (score, severity, gap, data age). |
top_gaps |
Up to 5 lowest-scoring, highest-weight dimensions. |
active_gates |
Gates currently firing (see below). |
layer_health |
Per-layer min/mean/status (e.g. L1_physiological). |
The dimensions scored are: commitments, sleep, training, goals, inbox, relationships, calendar, system_health β each by a dedicated scorer reading the relevant data file. The check-in uses top_gaps to generate data-driven questions rather than a hardcoded list: the worst 2-3 dimensions drive what gets asked. If a dimension is critical, ask about it directly.
Tip: Because equilibrium is deterministic and dependency-light, it's reusable outside the check-in (the Executive Loop runs it on a schedule). Run
python scripts/executive/equilibrium.pyfor a dashboard or--jsonfor the snapshot.
The sleep gate
The sleep gate is reported at the top of the check-in.
from scripts.scheduling.sleep_gate import check_sleep_gate
result = check_sleep_gate("max_effort_interval")
# result.is_blocked, result.avg_sleep_hours, result.days_with_data, result.message
check_sleep_gate(component) returns a SleepGateResult(is_blocked, avg_sleep_hours, days_with_data, message). Behavior (scripts/scheduling/sleep_gate.py):
- Computes a 7-day rolling sleep average from
data/garmin/YYYY-MM-DD.json(sleep.duration_hours). - Threshold is 6.5 hours (
SLEEP_THRESHOLD = 6.5), per Board ResolutionRES-mtg-c9207983-agenda-1. - Only high-intensity components engage the gate β
max_effort_interval(legacy),bike_intervals,intervals,hiit,sprints,ftp,max_effort. Other training types pass through unblocked. - No data β warn, don't block.
Threshold evaluation now delegates to the gate framework: check_sleep_gate calls scripts.executive.gates.is_action_blocked(f"training.{component}"), falling back to the direct avg < 6.5 check if the framework is unavailable. So the sleep gate is one declarative gate in a broader manifest rather than a standalone rule.
Note β the gate framework (
scripts/executive/gates.py): gates are declared indata/executive/equilibrium-manifest.jsonand evaluated deterministically. Gate types:hard(blocks actions β the sleep gate),soft(advisory),scope(caps capacity),tempo(reduces nudge frequency),upgrade(blocks adding new items).is_action_blocked("training.<component>")returns the first active gate that lists the action in itsblocks. See Executive Loop.
If the gate is active: report "sleep gate active (X.Xh avg) β high-intensity training suspended until sleep improves." See Sleep Guardian for the predictive side of sleep.
Step 1b: Sleep Guardian morning retrospective
Right after the gate status, surface last night's prediction vs. actual β so the guardian's learning is visible.
from scripts.scheduling.sleep_guardian_learn import get_morning_retrospective
retro = get_morning_retrospective()
if retro:
print(retro["summary"]) # one line unless asked for detail
get_morning_retrospective() (scripts/scheduling/sleep_guardian_learn.py) compares yesterday's risk assessment against the actual sleep recorded overnight, backfilling the outcome from Garmin if needed. It returns predicted_level, predicted_factors, actual_quality, prediction_correct, and a summary β or None if there's nothing to show.
If the guardian flagged yellow/red, the check-in may ask (standard/deep only) what bio-zack did differently, and record it:
from scripts.scheduling.sleep_guardian_learn import record_intervention
record_intervention("2026-01-15", ["magnesium", "no_screens_after_9pm"])
Natural language is mapped to a fixed key set: magnesium, no_screens_after_9pm, earlier_bedtime, no_caffeine_after_noon, wind_down_routine, melatonin, no_alcohol, exercise_before_5pm, breathwork, cool_room. The interventions feed the guardian's learning loop. See Sleep Guardian.
Step 2: Determine depth
| Gap since last check-in | Depth | Questions | Interaction style |
|---|---|---|---|
| < 12 hours | Quick | 2-3 | Batch (all at once) |
| 12-48 hours | Standard | 5-7 | Interactive (one at a time) |
| > 48 hours | Deep | 10+ | Interactive, catch-up |
Weekend mornings default to Deep regardless of gap β more time and bandwidth. Detailed logic in references/adaptive-depth.md.
Step 3: Gather pending items
Run these checks; all degrade gracefully β if any fails, the check-in continues without that source.
# scheduling drift β overdue / approaching-deadline goals
uv run python scripts/scheduling/cli.py drift-status --format=json --max-warnings=3
# pending commitments β past promises awaiting follow-up
uv run python scripts/scheduling/cli.py commitment-status --pending-only --format=json
Also read:
data/scheduling/pending_confirmations.jsonβ Garmin activities awaiting confirmation.scripts.objectives.get_daily()β today's daily objectives, to ask about incomplete ones (evening).
Scheduling drift
scripts/scheduling/drift_detector.py derives a status per goal from its frequency block:
| Field | Meaning |
|---|---|
target_interval_days |
Expected interval between occurrences. |
drift_warning_days |
When to start warning (often target - 1). |
tolerance_days |
Grace period after target (default 2). |
if days_since < warning_days:
status = ON_TRACK
elif days_since < target_days:
status = WARNING
elif days_since <= target_days + tolerance_days:
status = OVERDUE
else:
status = CRITICAL
CRITICAL/OVERDUE β ask first ("It's been X days since your last Y β any blockers, or should we schedule one?"). WARNING β after core questions. ON_TRACK with weekly progress β an FYI.
Pending commitments
scripts/scheduling/commitment_tracker.py drives follow-ups (max 3 per check-in):
- "You mentioned [text] for [date]. Did it happen?"
- Yes β close
done; No/Missed β closemissed; Rescheduled β new commitment, close old ascancelled.
Garmin confirmations
From data/scheduling/pending_confirmations.json (max 3 per check-in): auto-detected activities are presented for a yes/no/other so they can be logged as goal occurrences. Only activities from the last 7 days are surfaced; older ones are noted and auto-cleaned by a 14-day TTL. Missing/malformed file is skipped silently. Workflow detail in references/garmin-confirmations.md.
Step 4: Generate questions (priority order)
The order matters β don't bury overdue items under "how's your energy" small talk.
- CRITICAL/OVERDUE scheduling drift (ask first)
- Commitment follow-ups (max 3)
- Garmin confirmations (max 3)
- Incomplete daily objectives (evening)
- Active goals'
check_in_prompts - Weekly leadership/role-transition check (see Step 9)
- Recent-activity follow-up ("How did yesterday's [workout] feel?")
- Time-of-day questions β morning: sleep/energy/plans; evening: recap/stress
- WARNING items from drift
Full bank in references/question-sources.md.
Step 5: Ask questions
- Quick: all at once; accept a batch response.
- Standard/Deep: one at a time, conversational, allow tangents.
Step 6: Capture new commitments
Listen for future plans during the conversation and offer to save them (detail in references/commitment-capture.md):
- Future date + activity ("tomorrow", "Friday", "this weekend") + planning language ("I'll", "going to").
- Offer: "Note that as a commitment? (yes/no)". On yes, resolve goal + date and persist:
uv run python scripts/scheduling/cli.py add-commitment \
--goal=<goal-slug> \
--date=2026-01-10 \
--text="<placeholder commitment text>"
| Input | Resolved date |
|---|---|
| "tomorrow" | today + 1 |
| "Friday" | next Friday (or this Friday if before noon) |
| "this weekend" | Saturday |
| "next week" | next Monday |
Step 7: Save data
import sys; sys.path.insert(0, "scripts")
from utils import generate_uuid, atomic_write, safe_read_json
from datetime import datetime
entry = {
"id": generate_uuid(),
"timestamp": datetime.now().isoformat(),
"depth": "standard", # quick | standard | deep
"responses": [
{"question": "How did you sleep?", "answer": "<placeholder>"},
],
"insights_surfaced": [],
}
today = datetime.now().strftime("%Y-%m-%d")
path = f"data/checkins/{today}.json"
data = safe_read_json(path, {"entries": []})
data["entries"].append(entry)
atomic_write(path, data)
Always use scripts/utils.py helpers: atomic_write() (temp-file + rename), safe_read_json() (defaults on missing/malformed), generate_uuid(). See Data Integrity.
Step 8: Update scratch pad
Append to data/scratch/daily.md: a ## Check-ins line with timestamp + one-sentence summary, plus energy/sleep/priorities and any follow-ups set. Move surfaced insights from ## Pending Insights to ## Recently Surfaced Insights with the timestamp.
Note:
/checkinonly writes the day-grain surface (daily.md). Durable identity context (calibration notes, multi-week patterns) is folded intodata/executive/identity-state.mdby the nightly/sleepcycle, not by the check-in. See Scratch Pads and Improve / Sleep.
Step 9: Weekly leadership / role-transition check
A weekly-cadence question set, deterministically injected once per week (hash(date + "ceo") % 7 == 0), that tracks a leadership / role-transition goal. It rotates through a small bank of confidence/delegation prompts, with trigger-based overrides when certain conditions hold, and files responses to data/ceo-mode/daily/YYYY-MM-DD.json.
Note: This step's underlying goal (
goals/active/ceo-transformation-90day.json) was a fixed 90-day window whosetarget_datehas passed; the goal is stillstatus: active. Treat the step as possibly stale β verify the goal is still live before relying on it. The specific rotation questions and goal framing are work-internal and intentionally omitted here; document the mechanism (weekly deterministic injection β rotation/trigger questions βdata/ceo-mode/daily/), not the contents.
Step 10: Optional identity question (~20%)
Deterministic per day so multiple sessions agree and no seed file is needed:
# from .claude/skills/checkin/SKILL.md
ask_identity = hash(date + "vita") % 100 < 20 # ~20%, configurable via preferences
Note:
identity_question_probability(default0.2) andidentity_question_enabledlive inprofile/preferences.json. The expression above is the SKILL's. (An earlier version of this page showed asha256(...)-based formula β that was illustrative, not what the SKILL specifies.)
If asked: pick from meta/interview-questions.json where checkin: true, skip anything asked in the last 7 days, prefer the lowest-coverage domain. Save the answer to data/identity/interviews.jsonl with ctx: "checkin". If skipped, continue without blocking. Up to 3 identity observations may also be captured at the end (per the observation-capture skill). See Identity.
Data Structures
Check-in entry β data/checkins/YYYY-MM-DD.json
One JSON file per day; an entries array allows multiple check-ins per day. Numbers below are illustrative placeholders.
{
"entries": [
{
"id": "<uuid4>",
"timestamp": "2026-01-04T08:15:55",
"depth": "standard",
"responses": [
{"question": "Sleep quality", "answer": "<placeholder>", "source": "garmin"},
{"question": "Energy level", "answer": "<placeholder>"},
{"question": "Plans", "answer": "<placeholder>"}
],
"garmin_snapshot": {
"sleep_score": 0,
"sleep_hours": 0.0,
"hrv_ms": 0,
"hrv_status": "BALANCED"
},
"insights_surfaced": []
}
]
}
Shape notes:
- Quick entries are flat (no
garmin_snapshot, fewer responses presented as a batch). Standard/Deep entries includegarmin_snapshotwhen available and ask interactively. Analysis code must handle both shapes. responses[].source: "garmin"marks values pulled from telemetry rather than self-reported.
Goal with check_in_prompts
Goals drive the questions asked. Each goal carries a check_in_prompts array; its frequency block enables drift detection.
{
"id": "<uuid>",
"title": "Weekly hobby session",
"check_in_prompts": ["Did you get a session in this week?", "Any highlights?"],
"frequency": {
"type": "interval",
"target_interval_days": 7,
"drift_warning_days": 6,
"tolerance_days": 2,
"preferred_days": ["saturday", "sunday"]
},
"status": "active",
"priority": "high"
}
Weekly goal with components β goals/active/weekly-training-structure.json
A weekly frequency goal tracks multiple components, each with a target and aliases that map free-text activity names onto the component.
{
"id": "a7a038a5-09e3-436d-9684-2daec6a5702e",
"title": "Weekly training structure",
"weekly_targets": {"skate_sessions": 2, "bike": 3, "strength_days": 2},
"frequency": {
"type": "weekly",
"week_starts": "monday",
"components": {
"skate_sessions": {"target": 2, "aliases": ["skate", "skating"]},
"bike": {"target": 3, "aliases": ["bike", "cycling", "zone2", "interval", "hiit", "ftp", "running"]},
"strength_days": {"target": 2, "aliases": ["strength", "weights", "gym", "climbing"]}
}
}
}
Note: The live training structure has three components β
skate_sessions(2),bike(3),strength_days(2). The singlebikecomponent absorbs all cardio + interval/FTP activity via aliases. (Earlier docs listed four components βzone_2_cardioandmax_effort_intervalβ that no longer exist as goal components;intervals/hiit/ftp/cyclingare now aliases underbike. The legacy component names still appear only in the sleep gate'sHIGH_INTENSITY_COMPONENTSkeyword set.)
Commitment β data/assistant/commitments.json
schema_version: 2. Each entry: id, goal_id, text, scheduled_date, follow_up_date, source, status, created_at, closed_at, outcome (plus optional notes).
{
"schema_version": 2,
"commitments": [
{
"id": "cmt_<id>",
"goal_id": "<uuid>",
"text": "<placeholder commitment>",
"scheduled_date": "2026-01-10",
"follow_up_date": "2026-01-11",
"source": "checkin",
"status": "pending",
"created_at": "2026-01-03T10:00:00-08:00",
"closed_at": null,
"outcome": null
}
]
}
Occurrence record β data/scheduling/occurrences-YYYY-MM.jsonl
One JSON line per logged occurrence; component ties it to a weekly-goal component.
{"id":"occ_<id>","goal_id":"a7a038a5-09e3-436d-9684-2daec6a5702e","occurred_at":"2026-01-03T09:53:35-08:00","logged_at":"2026-01-03T12:01:08-08:00","source":"checkin","component":"bike","notes":"<placeholder>"}
Interview questions β meta/interview-questions.json
Domains β questions; only those with "checkin": true are eligible for the Step-10 identity question.
Preferences β profile/preferences.json
{
"checkin_times": ["morning"],
"tone": "balanced",
"focus_areas": ["fitness", "body_composition", "..."],
"identity_question_enabled": true,
"identity_question_probability": 0.2
}
Scheduling CLI Reference
All subcommands exist in scripts/scheduling/cli.py.
# drift
uv run python scripts/scheduling/cli.py drift-status --format=json --max-warnings=3
# commitments
uv run python scripts/scheduling/cli.py commitment-status --pending-only --format=json
uv run python scripts/scheduling/cli.py add-commitment --goal=<slug> --date=2026-01-10 --text="<text>"
uv run python scripts/scheduling/cli.py close-commitment cmt_<id> --outcome=done
# occurrence logging
uv run python scripts/scheduling/cli.py log-occurrence --goal=<slug> --component=bike --date=2026-01-03 --time=09:00
Occurrences can also be logged programmatically via scripts/scheduling/engine.py::log_occurrence(...). See the training-goals skill for component mapping.
Error Handling
| Scenario | Behavior |
|---|---|
data/scratch/daily.md missing |
Create with today's date heading |
| Goals directory empty | Skip goal questions |
| Check-in file malformed | Warn, recreate {"entries": []} |
| Drift / commitment CLI non-zero exit | Skip that source silently, continue |
pending_confirmations.json missing |
Skip Garmin confirmations |
| Equilibrium / guardian / objectives import fails | Skip that step, continue |
| Identity question declined | Continue without blocking |
All JSON writes use atomic_write() to prevent corruption on interruption; markdown appends are append-with-flush.
Design Rationale
- Adaptive depth keeps daily tracking from fatiguing β a morning quick-check needs 2-3 questions; a post-weekend catch-up benefits from a longer conversation.
- Equilibrium-driven questions mean the check-in asks about whatever is actually weakest today, instead of a static questionnaire that drifts out of relevance.
- Priority-ordered sources put accountability gaps (overdue commitments, drift) first, where they're hardest to ignore.
- Graceful degradation keeps the core loop (ask, save) working even when scheduling/equilibrium/guardian subsystems are down β they are enhancements, not prerequisites.
- Deterministic per-day randomness (hash of date) gives intra-day consistency, cross-day unpredictability, and zero external state.
- Free-text responses capture nuance ("slept terribly, woke up several times") that a rigid
{"sleep_quality": 3}would lose; structured extraction is a future enhancement.
Integration Points
| Subsystem | Connection |
|---|---|
| Executive Loop | compute_equilibrium() + the gate framework drive Step 1 and supply top_gaps. |
| Sleep Guardian | Morning retrospective (Step 1b) + the sleep gate (Step 1). |
Scheduling (scripts/scheduling/) |
Drift, commitments, occurrence logging (Steps 3, 6, 7). |
| Daily Objectives | get_daily() surfaces incomplete objectives (evening). |
| Garmin | Source of sleep/HRV/activity for the gate, snapshots, and confirmations. |
| Identity | Optional identity question + observation capture (Step 10). |
| Coaching | Check-in goal progress feeds proactive nudges (async; 12h cooldown per goal). |
Coaching
The coaching system (scripts/coaching/) runs asynchronously, not during the check-in, but consumes its output: goal progress feeds proactive.py (nudges goals with no progress for 7+ days), generator.py personalizes from identity data, and effectiveness.py enforces a 12-hour cooldown per goal (COOLDOWN_HOURS = 12).
Downstream consumers
Check-in data flows to: /week (weekly summary), /insights (energy/sleep/activity correlations), the Living Briefing daily brief, and the coaching system.
The Check-ins View (web UI)
Separate from the /checkin command, the web UI has a read-only Check-ins view (added Jun 2026) over the same data/checkins/ store. It aligns each day's self-report (energy, sleep quality, mood, notes) with same-day Garmin recovery so felt-vs-measured divergence is chartable.
| Concern | Location |
|---|---|
| API router | api/src/routers/health_checkins.py (prefix=/api/v1/health) |
| Service / parsing | api/src/services/health_checkins.py |
| Schemas | api/src/schemas/health_checkins.py |
| Web view | web/src/features/health/CheckinsView.tsx |
| Endpoint | GET /api/v1/health/checkins?days=N (API on port 33800) |
It handles both modern and legacy on-disk shapes and is honest about provenance: auto-generated "implicit" entries (synthesized from telemetry) are flagged as inferred and excluded from genuine self-report coverage counts. See Living Briefing and Garmin for the data feeding it.
File Locations
| Purpose | Path |
|---|---|
| Command (delegate) | .claude/commands/checkin.md |
| Skill (protocol) | .claude/skills/checkin/SKILL.md |
| Skill references | .claude/skills/checkin/references/*.md |
| Check-in data | data/checkins/YYYY-MM-DD.json |
| Equilibrium engine | scripts/executive/equilibrium.py |
| Gate framework | scripts/executive/gates.py (+ data/executive/equilibrium-manifest.json) |
| Sleep gate | scripts/scheduling/sleep_gate.py |
| Sleep guardian learning | scripts/scheduling/sleep_guardian_learn.py |
| Drift detector | scripts/scheduling/drift_detector.py |
| Commitment tracker | scripts/scheduling/commitment_tracker.py |
| Scheduling CLI / engine | scripts/scheduling/cli.py, engine.py |
| Objectives storage | scripts/objectives/storage.py (data/objectives/daily/) |
| Active goals | goals/active/*.json |
| Commitments | data/assistant/commitments.json |
| Pending confirmations | data/scheduling/pending_confirmations.json |
| Occurrences | data/scheduling/occurrences-YYYY-MM.jsonl |
| Interview questions | meta/interview-questions.json |
| Identity interviews | data/identity/interviews.jsonl |
| Preferences | profile/preferences.json |
| Working notes | data/scratch/daily.md |
| Utilities | scripts/utils.py |
| Web view | api/src/routers/health_checkins.py, web/src/features/health/CheckinsView.tsx |
Where to Go Next
- Executive Loop β the equilibrium engine and gate framework.
- Sleep Guardian β predictive sleep risk + the learning loop behind the retrospective.
- Daily Objectives β the objectives surfaced in evening check-ins.
- Goals β goal schema,
check_in_prompts, and frequency/drift fields. - Coaching β how check-in data drives proactive nudges.
- Garmin β the telemetry feeding sleep, HRV, and activity confirmations.