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-consolerouter lives on33800(API_PORTinrun;main.pymountstodo_console.routerthere). The code default forVITA_API_URL(today_api.py:23) is actuallyhttp://localhost:33801β that is the Vite web dev server, whosevite.config.tsproxies only/api,/data, and/s/to33800and does not forward/todo-console, so a default-config request to33801/todo-consolewould hit the web server rather than the FastAPI. SetVITA_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