Skip to content

Precedent Integration

Precedent is an external task and habit management system backed by an Obsidian vault (markdown files) with a Flask API. Endpoints live under http://localhost:5424/api (overridable via the PRECEDENT_URL env var). It is the canonical source of truth for tasks, habits, time tracking, meetings, and agenda views. Vita integrates deeply with Precedent through a Python client library, a full CLI, several automated systems, and a physical hardware console.

System Architecture

+-------------------+        +---------------------------+       +------------------------+
|   Precedent       |        |   today_api.py            |       |   Vita Consumers       |
|   (Flask, :5424)  | <----> |   (Python client)         | <---> |                        |
|   /api/*          |        |                           |       | - Silicon-zack (skill) |
|                   |        | create_task()             |       | - Executive loop       |
|   Obsidian Vault  |        | complete_task()           |       | - Auto-curator         |
|   (markdown files)|        | log_habit()               |       | - VDB daily brief      |
+-------------------+        | clock_in() / clock_out()  |       | - StreamDock buttons   |
                             | get_agenda()              |       | - Meeting processing   |
                             | get_meetings()            |       | - E-ink display        |
                             +---------------------------+       +------------------------+
                                        |
                                        | fire-and-forget refresh
                                        v
                             +---------------------------+
                             |   Vita API (FastAPI :33800)|
                             |   /todo-console/...       |
                             +---------------------------+
                                        |
                                        | push display rows
                                        v
                             +---------------------------+
                             |   Todo Console            |
                             |   (ESP32 + 4 OLEDs)      |
                             |   <console-host>          |
                             +---------------------------+

Note: The Python client never talks to the ESP32 directly. Mutating calls fire a best-effort POST to the vita FastAPI ({VITA_API_URL}/todo-console/refresh-if-changed, 5s timeout, daemon thread), and that router is what pushes display rows to the physical console β€” a two-hop refresh chain (today_api.py β†’ vita FastAPI (:33800) β†’ ESP32). The FastAPI/todo-console router lives on 33800 (API_PORT in run; main.py mounts todo_console.router there). The code default for VITA_API_URL (today_api.py:23) is actually http://localhost:33801 β€” that is the Vite web dev server, whose vite.config.ts proxies only /api, /data, and /s/ to 33800 and does not forward /todo-console, so a default-config request to 33801/todo-console would hit the web server rather than the FastAPI. Set VITA_API_URL=http://localhost:33800 (overridable via env) to target the FastAPI directly.

CLI

A first-class CLI (scripts/precedent/cli.py, also surfaced via the precedent shell command and the precedent-integration skill) wraps the client for interactive and agent use:

precedent task add|list|done|delete|update|note|clock-in|clock-out
precedent subtask add|done|delete
precedent checkin                 # log a habit report
precedent habit-update            # edit a logged habit report's note
precedent habits                  # show habits and recent reports
precedent agenda                  # meetings + tasks, merged
precedent projects                # list precedent projects
precedent meeting list|create|delete|show|tasks|notes
precedent goals                   # training/scheduling goals

The agenda subcommand merges meetings and task time entries into one timeline; the meeting subcommands operate on the markdown meeting files described below.

Task Management

Tasks live in mutually exclusive buckets within Precedent:

Bucket Meaning
in_progress Actively working on
next_actions Ready to work, no date set
scheduled Has a scheduled_date β€” revisit then
maybe Someday/maybe list
completed Done

Task Fields

Field Notes
key 8-char alphanumeric ID (e.g., KyQzQo0P)
text Task description
project_path e.g. "vita", "health", "" (default/personal) β€” valid paths from precedent projects
status null or "in_progress"
due_date Hard deadline (YYYY-MM-DD)
scheduled_date When to revisit (YYYY-MM-DD)
subtasks {text, completed} objects
notes Strings (often URLs)
tags Tag strings (e.g., "console", "auto", "next")
time_entries [start_iso, end_iso] pairs
current_session ISO timestamp if currently clocked in

CRUD Operations

from scripts.integrations.today_api import (
    create_task, complete_task, delete_task, update_task,
    get_tasks, get_tasks_by_project, add_task_note,
    add_subtask, update_subtask, delete_subtask,
    TodayAPIError,
)

# Create
result = create_task(
    text="Review PR",
    due_date="2026-02-25",
    scheduled_date="2026-02-24",
    project_path="vita",
    tags=["console"],
)
# result: {"key": "abc123", "text": "Review PR", ...}

# List (returns all buckets)
tasks = get_tasks()
# {"in_progress": [...], "next_actions": [...], "scheduled": [...], ...}

# Filter by project
tasks = get_tasks_by_project("vita")

# Complete / reopen
complete_task("abc123", completed=True)
complete_task("abc123", completed=False)  # reopen

# Delete
delete_task("abc123")

# Update fields
update_task("abc123", {"text": "New text", "tags": ["console", "next"]})

# Notes
add_task_note("abc123", "https://github.com/org/repo/pull/42")

# Subtasks
add_subtask("abc123", "Read snap-fit article")
update_subtask("abc123", index=0, completed=True)
delete_subtask("abc123", index=0)

Time Tracking

Tasks have built-in clock in/out for time tracking. Clocking in also sets the task status to in_progress.

from scripts.integrations.today_api import clock_in, clock_out

clock_in("abc123")                    # Start timer, set in_progress
clock_out("abc123", complete=False)   # Stop timer
clock_out("abc123", complete=True)    # Stop timer and mark done

Time entries are stored as [start_iso, end_iso] pairs in the task's time_entries array. The current_session field holds the start timestamp while clocked in; it is cleared on clock out.

The todo console hardware shows elapsed time as a [HH:MM] prefix on clocked-in tasks, and the auto-curator uses clock recency for scoring.

Habit Logging

Habits are predefined in Precedent. Each habit can be logged once per day with an optional freeform note. Habit keys are short slugs; the catalog mixes recurring health-routine entries with activity habits.

Known Habits

Key Name Note field
med1 Routine 1 freeform note
med2 Routine 2 freeform note
med3 Routine 3 freeform note
strengt Strength "push", "pull", "legs", or workout notes
skate Skate Rating 1-5 or location
run Run Distance/duration
sauna Sauna Duration in minutes
sbtatio Stationary bike Duration
note Note Freeform daily note
tsake Take supplements freeform note

Note: The first three keys above are private health-routine entries; their real slugs and note contents are intentionally not reproduced here. Reimplementers should treat the habit catalog as an opaque list of {key, name, note} rows.

Logging

from scripts.integrations.today_api import (
    log_habit, update_habit_report, get_habits, get_habits_for_display,
    get_habit_reports, TodayAPIError,
)

# Log today
log_habit("strengt", note="push")
log_habit("note", note="good energy")

# Backdate
log_habit("skate", note="4", date_str="2026-02-20")

# Already logged? Update the note
try:
    log_habit("skate", note="3")
except TodayAPIError as e:
    if "HTTP 409" in str(e):
        update_habit_report("skate", "2026-02-24", note="3")

# Query
habits = get_habits_for_display()
# [{"key": "strengt", "name": "Strength", "completed_today": True, "last_report": "2026-02-24"}, ...]

# Date range reports
reports = get_habit_reports("2026-02-01", "2026-02-24")
# [{"key": "strengt", "name": "Strength", "icon": "...", "reports": [{"date": "2026-02-03", "note": "push"}, ...]}, ...]

When logging physical activity habits (skate, strength, run, bike), silicon-zack also logs training goal occurrences. See the training-goals skill for the mapping.

Agenda Views

The agenda combines meetings and task time entries into a unified daily/weekly view.

from scripts.integrations.today_api import get_agenda

# Built-in timeframes
agenda = get_agenda("today")
agenda = get_agenda("yesterday")
agenda = get_agenda("this_week")
agenda = get_agenda("last_week")

# Custom range
agenda = get_agenda("custom", start_date="2026-02-01", end_date="2026-02-24")

# Structure
for date_str, day in agenda.get("days", {}).items():
    stats = day.get("stats", {})
    # stats: {meetings, tasks_clocked, tasks_completed, total_minutes}
    for item in day.get("items", []):
        # item types: "meeting", "task", "completed_task"
        print(f"{item['type']}: {item['title']}")

The VDB daily brief uses get_today_for_vdb() to pull task and habit summary counts into the morning briefing.

Meeting Management

Meetings are markdown files in Precedent, organized by project folders.

from scripts.integrations.today_api import (
    create_meeting, delete_meeting, get_meetings,
    get_meeting_content, get_meeting_transcript, get_meeting_tasks,
    update_meeting_notes, update_meeting_metadata,
)

# Create
create_meeting("Sync 1:1", "2026-02-25", start_time="14:00", end_time="15:00",
               attendees=["Person A"], folder_path="work/meetings")

# List (with optional date filter)
meetings = get_meetings("2026-02-01", "2026-02-28")
# [{name, date, duration, attendees, path, meeting_start, meeting_end}, ...]

# Read content (notes + transcript)
content = get_meeting_content("work/meetings/2026-02-25 Sync 1-1.md")
transcript = get_meeting_transcript("work/meetings/2026-02-25 Sync 1-1.md")
tasks = get_meeting_tasks("work/meetings/2026-02-25 Sync 1-1.md")

# Update
update_meeting_notes("meetings/2026-02-25 Sync 1-1.md", content="## Agenda\n- Topic 1")
update_meeting_metadata("meetings/2026-02-25 Sync 1-1.md", {
    "meeting_start": "2026-02-25 14:00:00",
    "attendees": ["Person A", "Person B"],
})

# Delete
delete_meeting("meetings/2026-02-25 Sync 1-1.md")

Meetings support both single-file (.md) and folder-structured (folder/notes.md + folder/transcript.md) formats. The client handles both transparently.

Todo Console (Hardware)

An ESP32 microcontroller at <console-host> drives 4 OLED displays (128x32 pixels each), each with left and right buttons. It bridges physical interaction with Precedent tasks. The device URL is resolved from the device registry, with a configurable fallback (VITA_TODO_CONSOLE_FALLBACK_URL, default http://todo-console.local).

Button Actions

Button Press Action
Left Short/Long Toggle clock in/out (auto-clocks-out other tasks)
Right Short Mark task completed
Right Long Delete task
Bottom-left Long Next page of tasks

Button-to-action resolution lives in resolve_button_action() (api/src/services/todo_console_actions.py): button 0 β†’ toggle_clock, button 1 + long press β†’ delete, button 1 short β†’ complete.

Display Modes

The console can be in several mutually-exclusive states, each backed by its own in-memory page offset:

Console mode (default): Shows tasks tagged "console" from in_progress and next_actions. Display priority: clocked-in task first, then tasks tagged "next" (pinned), then others by recency.

Project focus mode: Shows all tasks for a specific project (not limited to console-tagged). Activated via the /focus/{project} API or a StreamDock button.

Tag focus mode: Shows all tasks carrying an arbitrary tag (e.g. "weekend"). Activated via /focus-tag/{tag}.

Focus mode (task views): Shows a single task view β€” one of due, scheduled, in_progress, next_actions β€” for StreamDock view buttons. Activated via /focus-mode/{mode}.

Auto mode: Shows tasks tagged "auto" by the auto-curator. Cross-project view with project name prefixed in brackets (e.g., [Project] Review PR).

Activating any focus/auto state clears the others, so the console is always in exactly one mode.

Pagination

Each mode supports pagination through 4-task pages. The bottom-left long press advances to the next page, wrapping to the start when past the end. The last display row shows a (N+) suffix indicating remaining tasks.

Display Formatting

  • Clocked-in tasks show elapsed time as [HH:MM] prefix and are inverted (white on black)
  • Priority tasks show a >> prefix
  • Auto mode tasks show project in brackets: [Project]
  • Text wraps within the 4-line, ~21 char-per-line OLED display (up to 80 chars total)

Console API Endpoints

All under the vita FastAPI /todo-console prefix (api/src/routers/todo_console.py):

Endpoint Method Description
/callback POST Handle ESP32 button presses
/refresh POST Manually refresh all displays
/refresh-if-changed POST Conditional refresh (avoids flicker)
/shuffle POST Randomize task selection
/status GET Current display state for debugging
/tasks POST Create tasks with console tag
/set-order POST Manually set display order
/pin/{key} POST Add "next" tag (pin to top)
/unpin/{key} POST Remove "next" tag
/add/{key} POST Add "console" tag to existing task
/focus/{project} POST Enter project focus mode
/unfocus POST Exit project focus, return to console mode
/project-next-page POST Next page within focused project
/project-page-one POST Jump to first page of focused project
/focus-tag/{tag} POST Enter tag focus mode
/unfocus-tag POST Exit tag focus mode
/focus-mode/{mode} POST Enter a task-view focus (due/scheduled/in_progress/next_actions)
/unfocus-mode POST Clear task-view focus
/focus-status GET Current focus state (project, tag, auto, focus mode)
/auto-mode POST Toggle auto mode
/auto-next-page POST Next page within auto mode
/auto-status GET Current auto-mode state
/task-counts GET Task counts for StreamDock icon rendering

Auto-Curator

The auto-curator (scripts/scheduling/auto_curator.py) runs every 60 minutes during business hours (7am-6pm PT, scheduled in api/src/scheduler.py) to select the most actionable tasks across all projects. It scores tasks on six weighted dimensions:

Dimension Weight What it measures
Due date 0.30 Proximity to deadline (overdue = 1.0, this week = 0.7)
Scheduled 0.20 Scheduled date vs today (past/today = 1.0, future = 0.0)
Recency 0.15 How recently created (< 24h = 1.0, > 1 week = 0.2)
Status 0.15 Bucket priority (in_progress = 1.0, next_actions = 0.6)
Engagement 0.10 Ignored tasks get deprioritized (5+ ignores = 0.1)
Structure 0.10 Has subtasks/notes (subtasks = 0.8)

Constraints: MAX_AUTO_TASKS = 8, MAX_PER_PROJECT = 3 (diversity cap). Currently clocked-in tasks are always included. Results are tagged with "auto" in Precedent and logged to data/auto-curator/curation.jsonl. Engagement tracking in data/auto-curator/engagement.jsonl feeds back into scoring.

Integration Points

Executive Loop

The executive task manager (scripts/executive/task_manager.py) bridges equilibrium gaps to Precedent tasks. When a dimension (sleep, commitments, training, etc.) scores below the gap threshold (default 75, configurable from the executive manifest), it creates or updates a Precedent task in the vita project tagged with eq:<dimension>. The executive loop picks the top ready task each cycle. See Executive Loop.

VDB Daily Brief

The morning brief calls get_today_for_vdb() to include task and habit summary counts (tasks pending, in progress, habits completed today/yesterday).

StreamDock

The StreamDock manager (scripts/streamdock/manager.py) reads projects and tasks from Precedent for its physical button grid. It shows the currently clocked-in task, habit completion counts, and project/view focus buttons that drive the todo console (using the /focus, /focus-mode, /auto-mode, and /task-counts endpoints above). See Peripherals.

Console Refresh

Every mutating operation in today_api.py (create, complete, delete, update, clock in/out, subtask changes) fires a background refresh-if-changed request to the vita API, which recomputes the display and pushes rows to the ESP32 β€” keeping the physical display in sync without blocking the caller.

Error Handling

Error Meaning Response
HTTP 400 Bad request (project doesn't exist, invalid data) Check project_path
HTTP 404 Resource not found Task/habit key doesn't exist
HTTP 409 Conflict (habit already logged today) Use update_habit_report() instead
Connection error API unreachable Warn user, try later
Timeout Request exceeded 15 seconds (TIMEOUT) Retry or warn

All client errors surface as TodayAPIError.

Status Check

uv run python scripts/today-sync.py         # Full status (habits + tasks)
uv run python scripts/today-sync.py --test  # Connectivity only

File Locations

File Purpose
scripts/integrations/today_api.py Python client for Precedent API (BASE_URL, PRECEDENT_URL, VITA_API_URL)
scripts/precedent/cli.py Precedent CLI (task/subtask/checkin/habits/agenda/meeting/goals)
scripts/today-sync.py Status check / connectivity test
api/src/routers/todo_console.py Todo console hardware bridge (ESP32 API)
api/src/services/todo_console_actions.py Button-action resolution
scripts/scheduling/auto_curator.py Auto-curator scoring and tagging
api/src/scheduler.py Auto-curator scheduling loop (60m, 7am-6pm PT)
scripts/executive/task_manager.py Executive loop task bridge (eq:<dimension>)
scripts/streamdock/manager.py StreamDock button integration
scripts/vdb/data.py VDB brief data gathering (get_today_for_vdb)
.claude/skills/precedent-integration/SKILL.md Silicon-zack skill definition
.claude/skills/precedent-integration/reference.md Full API reference with examples
data/auto-curator/curation.jsonl Curation decision log
data/auto-curator/engagement.jsonl Console engagement tracking

Where to go next

  • Executive Loop β€” equilibrium gaps that flow into Precedent tasks
  • Peripherals β€” StreamDock and other hardware controllers
  • E-ink Display β€” objectives surface driven from Precedent data
  • Objectives β€” daily/weekly objectives that soft-link into tasks