Skip to content

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:

  1. 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.
  2. 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.
  3. Proactive accountability β€” scheduling drift, overdue commitments, and unconfirmed activities are surfaced first, before small talk.
  4. 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.py for a dashboard or --json for 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 Resolution RES-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 in data/executive/equilibrium-manifest.json and 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 its blocks. 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):

  1. "You mentioned [text] for [date]. Did it happen?"
  2. Yes β†’ close done; No/Missed β†’ close missed; Rescheduled β†’ new commitment, close old as cancelled.

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.

  1. CRITICAL/OVERDUE scheduling drift (ask first)
  2. Commitment follow-ups (max 3)
  3. Garmin confirmations (max 3)
  4. Incomplete daily objectives (evening)
  5. Active goals' check_in_prompts
  6. Weekly leadership/role-transition check (see Step 9)
  7. Recent-activity follow-up ("How did yesterday's [workout] feel?")
  8. Time-of-day questions β€” morning: sleep/energy/plans; evening: recap/stress
  9. 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: /checkin only writes the day-grain surface (daily.md). Durable identity context (calibration notes, multi-week patterns) is folded into data/executive/identity-state.md by the nightly /sleep cycle, 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 whose target_date has passed; the goal is still status: 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 (default 0.2) and identity_question_enabled live in profile/preferences.json. The expression above is the SKILL's. (An earlier version of this page showed a sha256(...)-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 include garmin_snapshot when 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 single bike component absorbs all cardio + interval/FTP activity via aliases. (Earlier docs listed four components β€” zone_2_cardio and max_effort_interval β€” that no longer exist as goal components; intervals/hiit/ftp/cycling are now aliases under bike. The legacy component names still appear only in the sleep gate's HIGH_INTENSITY_COMPONENTS keyword 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.