Self-Improvement System
VITA is a self-improving personal health operating system. This document describes the proposal development, scoring, and agent-assisted refinement workflow that enables continuous system enhancement.
Overview
The Self-Improvement System generates, evaluates, and refines feature proposals for VITA. It operates through:
/improveβ manual, on-demand: list, view, develop N new proposals via parallel agents, and re-score/sleepβ the autonomous nightly Stepwise flow that reflects on the day's data, revises and reranks proposals, conditionally generates new ones, and autonomously builds one small improvement- Web kanban board for visual proposal management
- Conversational agents that auto-spawn to respond to user comments on a proposal
The key innovation is the agent-per-comment model: when a user adds a comment to any proposal via the web UI, a background Claude agent spawns to read the proposal, address the feedback, and reply. This creates a conversational refinement loop without requiring manual /improve runs.
Architecture
+-----------------------+
| Web Kanban UI |
| /proposals route |
+-----------+-----------+
|
+-------------+ +-----------v-----------+
| /improve | | FastAPI Router |
| /sleep +----->+ proposals.py |
| CLI commands| +-----------+-----------+
+-------------+ |
+-----------v-----------+
| Services |
| - ProposalsService |
| - ProposalStateService
| - ClaudeRunnerService|
+-----------+-----------+
|
+-------------+------------+-----------+-------------+
| | | |
+------v------+ +----v----+ +-------v------+ +----v----+
| proposal- | |proposals| |proposal- | |Claude |
| state.json | |/*.md | |scores.json | |CLI |
+-------------+ +---------+ +--------------+ +---------+
Key Components
| Component | Location | Purpose |
|---|---|---|
| ProposalsService | api/src/services/proposals.py |
Parse markdown proposals, extract summaries |
| ProposalStateService | api/src/services/proposal_state.py |
Kanban lanes, comments, agent lifecycle |
| ClaudeRunnerService | api/src/services/claude_runner.py |
Spawn/manage Claude CLI sessions |
| ImproveCycleService | api/src/services/improve_cycle.py |
Batch revision via API (legacy) |
| Proposals Router | api/src/routers/proposals.py |
REST endpoints + agent orchestration |
The Comment-Triggered Agent Model
This is the core interaction pattern for proposal refinement:
User adds comment API receives POST Agent spawns
in Web UI /{filename}/comments in background
| | |
v v v
+----------+ +---------------+ +------------+
| "Add | HTTP POST | 1. Validate | asyncio | Claude CLI |
| Telegram |------------->| 2. Store |---------->| with prompt|
| support" | | 3. Cancel old | | and tools |
+----------+ | 4. Start new | +-----+------+
+---------------+ |
v
Agent response +------------+
via POST /comments | 1. Read |
with author=system | proposal|
^ | 2. Search |
| | codebase|
+----------------| 3. Edit |
| 4. Reply |
+------------+
Agent Lifecycle States
start_agent()
|
v
+----------+ +---------+ update_agent_session() +----------+
| inactive | | pending |---------------------------> | running |
+----------+ +---------+ +----+-----+
^ |
| |
+------------------cancel_agent()------------------------+
| |
+---------------clear_agent_if_session()-----------------+
(guards against stale callbacks)
The session-guarded cleanup is critical: when a user posts a new comment while an agent is running, the old agent is killed and a new one spawns. The clear_agent_if_session(session_id) method prevents race conditions where a stale agent's completion callback might clear the new agent's state.
Agent Prompt Template
When an agent spawns, it receives this prompt (simplified):
# Proposal Agent - Run {run_id}
You are helping refine a VITA improvement proposal.
## Proposal
File: plans/proposals/{filename}
## Recent Comments
[last 10 comments with timestamps and authors]
## Instructions
1. Read the proposal first
2. Understand the context
3. Address the latest comment:
- If question -> answer it
- If feedback -> incorporate into plan
- If investigation request -> search codebase
4. You can search with Glob/Grep/Read, edit the proposal, or close it
5. MUST add a response comment when done
Trigger Points
| Command | Mode | Description |
|---|---|---|
/improve |
Interactive | List proposals, choose to view or develop new ones |
/improve view |
View | Open proposal-rankings.html in browser |
/improve develop N |
Develop | Spawn N sub-agents to generate unique proposals |
/improve score |
Score | Re-score all proposals, regenerate rankings |
/sleep |
Autonomous | Nightly Stepwise flow: gather β reflect β revise β conditionally generate β build (see /sleep section) |
When to Use Each Mode
| Mode | Frequency | Trigger |
|---|---|---|
| Web comments | Real-time | Any time you have feedback on a specific proposal |
/improve develop N |
On demand | When seeking new ideas |
/improve score |
After manual edits | Re-evaluate changed proposals |
/sleep |
Weekly | After significant new data accumulation |
Kanban Board
Lanes
| Lane | Meaning | Visual Style |
|---|---|---|
triage |
New proposals awaiting review | Gray/slate |
ready |
Approved, ready to implement | Green/emerald |
needs_work |
Requires changes before approval | Amber/orange |
maybe_later |
Deprioritized for now | Blue/sky |
done |
Completed or abandoned | Purple/violet |
Proposal Cards Display
Cards show: - Title (links to full detail view) - Priority badge (P0-P4 color-coded) - Effort badge (hours, day, days, week, weeks) - Score (weighted rubric score if calculated) - Domain/type tags - Abstract (first ~500 chars of Problem Statement) - Comment thread with user/system distinction - Agent status indicator (spinner when processing)
Drag-and-Drop
Cards can be dragged between lanes. Position within lane is preserved. State updates are atomic (temp file + rename pattern).
REST API
Base path: /api/v1/proposals
Core Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | / |
List all proposals (optional ?status= filter) |
| GET | /{filename} |
Get single proposal with full markdown content |
| GET | /state |
Get kanban board state (lanes, comments, processing) |
| PUT | /move |
Move proposal to target lane at position |
| PUT | /reorder |
Reorder proposals within a lane |
| POST | /create |
Create stub proposal from one-line idea |
| DELETE | /{filename} |
Delete proposal (file + state) |
Agent/Comment Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /{filename}/comments |
Get comments for proposal |
| POST | /{filename}/comments |
Add comment (triggers agent if author=user) |
| POST | /{filename}/close |
Move to done lane (for agent use) |
| GET | /processing-status |
Get agent status (active, proposal, session_id) |
| POST | /improve |
Start scoring cycle via Claude CLI |
Example: Add Comment and Trigger Agent
The API listens on port 33800 (./run api). Port 33801 is the Vite web UI, 33802 is the public read-only API β they are not the proposals API.
# Add a user comment (triggers agent automatically)
curl -X POST http://localhost:33800/api/v1/proposals/2026-01-01-token-dashboard.md/comments \
-H "Content-Type: application/json" \
-d '{"text": "Can you add a weekly view?", "author": "user"}'
# Response: updated KanbanState with new comment
# Agent spawns in background, will add "system" comment when done
# Check if agent is still running
curl http://localhost:33800/api/v1/proposals/processing-status
# {"active": true, "started_at": "...", "proposal": "2026-01-01-token-dashboard.md", "session_id": "abc123"}
Data Structures
Proposal State (proposal-state.json)
{
"schema_version": 1,
"updated_at": "2026-01-04T20:18:16.512744",
"lanes": {
"triage": ["2026-01-02-layered-data-architecture.md", ...],
"ready": ["2026-01-01-token-usage-dashboard.md"],
"needs_work": [...],
"maybe_later": [],
"done": ["vdb-web-ui.md"]
},
"comments": {
"2026-01-01-proactive-gap-filler.md": [
{
"id": "e52ac5dc",
"ts": "2026-01-02T04:10:30.347554",
"author": "user",
"text": "hit me up on telegram with these questions",
"processed": false
},
{
"id": "59331726",
"ts": "2026-01-04T20:18:07.555911",
"author": "system",
"text": "Acknowledged! The proposal agent is working...",
"processed": true
}
]
},
"processing": {
"active": false,
"started_at": null,
"proposal": null,
"session_id": null
}
}
Processing State Fields
| Field | Type | Purpose |
|---|---|---|
active |
bool | Agent is currently running |
started_at |
ISO-8601 | When agent started (for staleness detection) |
proposal |
string | Filename being processed |
session_id |
string | Claude session ID for kill/guard operations |
The session_id starts as "pending" when the agent is spawning, then updates to the real session ID once the Claude process starts. This prevents race conditions in the completion callback.
Comment Fields
| Field | Type | Purpose |
|---|---|---|
id |
string | 8-char hex for reference |
ts |
ISO-8601 | Creation timestamp |
author |
"user" or "system" | Who wrote it |
text |
string | Comment content |
processed |
bool | Has this been addressed by an agent? |
Scoring
Evaluation Rubric
Proposals are scored using an 8-criterion weighted rubric:
| Criterion | Weight | Description |
|---|---|---|
| user_impact | 0.20 | How significantly improves health outcomes? |
| vita_alignment | 0.15 | Aligns with proactive, self-improving, data-centric philosophy? |
| data_leverage | 0.15 | Uses existing Garmin/check-in/identity data? |
| feasibility | 0.15 | Realistic implementation? |
| novelty | 0.10 | Unique compared to other proposals? |
| immediate_relevance | 0.10 | Relevant to current goals? |
| specification_quality | 0.10 | Well-specified with clear UX? |
| risk_awareness | 0.05 | Acknowledges and mitigates risks? |
Score Calculation
# Base score (0-10 range, weighted average)
base_score = sum(score[criterion] * weight[criterion] for each criterion)
# Bonuses
+1 if enhances other proposals (synergy)
+1 if Phase 1 delivers in <1 day (quick_win)
+1 if uses identity/observation data (identity_integration)
# Penalties
-2 if duplicates another proposal
-1 if tries to do too much (scope_creep)
total_score = base_score + bonuses - penalties
Error Handling
Stale Agent Cleanup
Agents are auto-cleared after 5 minutes of being "active":
if processing.get("active") and processing.get("started_at"):
started = datetime.fromisoformat(processing["started_at"])
elapsed_minutes = (datetime.now() - started).total_seconds() / 60
if elapsed_minutes > 5:
self.set_processing_state(active=False)
return default_processing
Session-Guarded Cleanup
The clear_agent_if_session(session_id) method prevents stale callbacks:
def clear_agent_if_session(self, session_id: str) -> bool:
"""Clear agent state only if session_id matches current.
Prevents stale callbacks from clearing newer agent state.
"""
state = self._read_state()
current_session = state.get("processing", {}).get("session_id")
if current_session != session_id:
return False # Stale callback, ignore
# Session matches, safe to clear
state["processing"] = {...cleared...}
self._write_state(state)
return True
Orphaned Proposal Sync
On every get_kanban_state() call:
1. New .md files in plans/proposals/ are added to top of triage
2. Deleted files are removed from all lanes
3. Comments for deleted files are cleaned up
Atomic Writes
All state file writes use temp file + rename:
tmp_path = self.state_file.with_suffix(".tmp")
with open(tmp_path, "w") as f:
json.dump(state, f, indent=2)
tmp_path.rename(self.state_file) # Atomic on POSIX
File Locations
| File | Purpose |
|---|---|
plans/proposals/*.md |
Individual proposal markdown files |
plans/proposals/*.html |
Rendered HTML versions (generated) |
plans/proposals/archived/ |
Archived (auto- or manually-retired) proposals |
plans/proposal-rankings.html |
Master rankings dashboard |
plans/proposal-state.json |
Kanban state (lanes, comments, processing) |
plans/proposal-scores.json |
Cached scores (mtime-based invalidation) |
plans/PROPOSAL_SCHEMA.md |
Template for new proposals |
plans/EVALUATION_RUBRIC.json |
Scoring criteria and weights |
scripts/render_proposals.py |
Render proposal markdown to styled HTML |
meta/FEATURE_INTENT.md |
Aspirational feature goals (referenced/audited by /sleep) |
api/src/routers/proposals.py |
REST API endpoints |
api/src/services/proposals.py |
Proposal parsing service |
api/src/services/proposal_state.py |
Kanban state + agent lifecycle |
api/src/services/claude_runner.py |
Claude CLI session management |
api/src/services/improve_cycle.py |
Batch revision via API (ImproveCycleService) |
web/src/components/kanban/ |
React kanban board components |
flows/sleep/FLOW.yaml |
/sleep flow orchestration (Stepwise) |
flows/sleep/prompts/*.md |
Agent step prompts (reflect, prompt_review, revise_proposals, build, web_discover) |
scripts/sleep/preprocess.py |
Builds the data/sleep/staging/* files |
scripts/sleep/repair_corrupted_revisions.py |
Decodes JSON-escaped revision blocks back to markdown |
scripts/executive/detect_stale_systemic_issues.py |
Identity-state staleness antibody |
scripts/sleep/feature_intent_gaps.py |
Feature-intent gap-watch antibody |
data/sleep/staging/ |
Inter-step staging files (proposal index, antibody reports, β¦) |
.claude/skills/improve/ |
/improve skill (subcommands + develop workflow) |
Known Limitations
Current Gaps
-
No score persistence after scoring - Scores are recalculated each
/improve scorerun rather than cached per-proposal with change detection -
No duplicate detection - Sub-agents are told not to duplicate but there's no automated similarity check before creation
-
Single agent per proposal - Only one agent can process a proposal at a time; rapid comments queue behind each other
-
Comments not marked processed - The agent model doesn't currently mark user comments as "processed" after responding
-
No agent streaming to UI - Agent output goes to server logs, not streamed to the web UI (planned)
-
No proposal dependencies - No way to mark "build X before Y" relationships
-
Limited auto-archival - Partly addressed:
plans/proposals/archived/exists andpreprocess.pyauto-archives proposals with 5+ consecutive archive recommendations. Proposals not hitting that threshold still accumulate inplans/proposals/.
Improvement Opportunities
-
Stream agent output to UI - Use SSE endpoint (
/stream?session_id=...) to show agent thinking in real-time -
Embeddings-based duplicate detection - Compare new proposal embeddings against existing before writing
-
Incremental scoring - Only re-score proposals modified since last scoring run
-
Proposal dependencies graph - Allow explicit "depends on" relationships and visualize in rankings
-
Comment threading - Allow replies to comments, not just flat list
-
Version history - Track proposal revisions with diffs
-
Agent memory - Let agents remember context from previous runs on the same proposal
-
Multi-agent coordination - Allow parallel agents on different proposals with file locking
/sleep: the Nightly Reflection Flow
/sleep is the autonomous half of the self-improvement system. It is not a single batch script β it is a multi-step Stepwise flow defined at flows/sleep/FLOW.yaml, run nightly (~00:30 PT). It superseded the executive scheduler's old _run_sleep orchestration. The flow entrypoint is the YAML; the agent steps are driven by prompt files in flows/sleep/prompts/*.md.
A reader reimplementing this should treat flows/sleep/FLOW.yaml as the orchestration contract and the prompt files as the agent instructions.
Flow Steps
Steps are a mix of deterministic Python scripts (run:) and agent steps (executor: agent, with a prompt_file). Several script steps run in parallel after preprocess; the four agent steps share one Claude session (session: sleep) so context built in reflect carries forward.
| Step | Kind | What it does |
|---|---|---|
stepwise_snapshot |
script | Snapshot stepwise.db for crash recovery (keeps last 10) |
preprocess |
script | Build the compact staging files under data/sleep/staging/ (proposal index, meetings, commitments, codebase graph, antibody reports) |
commitment_drain_staging |
script | Stage overdue/untriaged commitment-drain candidates (never mutates commitments.json) |
heartbeat_archive_dead |
script | Auto-dismiss heartbeat topics with β₯8 nudges and zero engagement |
view_freshness_coverage |
script | Audit memory/views/* against the liveness contract; write a staleness report |
trigger_suppression_refresh |
script | Auto-suppress proactive triggers with β₯10 sends and 0 responses |
data_source_health |
script | Aggregate source_state across all sync surfaces; surface auth/parse errors |
web_discover |
agent | Browse X/HN via Camofox for overnight discoveries (~15 min, cost-capped) |
git_snapshot |
script | Pre-sleep git snapshot for rollback safety |
reflect |
agent | Data gathering + pattern analysis + meetings + commitments + forge + sentinels (phases 2β3) |
prompt_review |
agent | Audit system-prompt health β voice drift, tool discipline, identity anchoring |
engagement_aggregator |
script | Aggregate per-voice composer engagement (last 7 days) |
voice_drift_audit |
script | Blind voice-classification audit (last 7 days) |
revise_proposals |
agent | Revise proposals, rerank, update work context, conditionally generate new (phases 4β6) |
build |
agent | Autonomously implement one small improvement (phase 7), only if threshold met |
finalize |
script | Git commit all changes + write the overnight summary JSON for the briefing |
Note: The four agent steps (
web_discover,reflect,prompt_review,revise_proposals,build) are where the LLM does work; the rest are deterministic. Frontier models in use areclaude-opus-4-8/claude-fable-5β see Model Guidance for routing.
Staging Architecture (the data-passing contract)
/sleep steps don't pass data in memory β they communicate through files. This is the actual contract a reimplementer must honor:
| File | Written by | Consumed by | Contents |
|---|---|---|---|
data/sleep/staging/proposal-index.json |
preprocess.py |
revise_proposals |
Per-proposal metadata + skip_revision flags, skip_revision_count, auto_archive_triggered |
data/sleep/staging/meeting-summaries.json |
preprocess.py |
reflect |
Pre-extracted meeting commitments/outcomes |
data/sleep/staging/commitment-triage.json |
preprocess.py |
reflect |
Pre-computed overdue/stale triage |
data/sleep/staging/view-freshness-{date}.json |
view_freshness_coverage.py |
reflect |
Per-view staleness report |
data/sleep/staging/identity-state-staleness.json |
preprocess.py (antibody) |
reflect |
Staleness audit of identity-state.md (see below) |
data/sleep/staging/feature-intent-gaps.json |
preprocess.py (antibody) |
reflect |
Feature commits not reflected in FEATURE_INTENT.md |
data/sleep/staging/homeostasis.json |
preprocess.py |
reflect |
Manifesto equilibrium metrics (engagement, ack rates, prediction calibration) |
.stepwise/sleep-reflection.json |
reflect |
revise_proposals, finalize |
Structured reflection summary (health, patterns, forge, board, FLL) with real values |
The reflect agent's only durable output is .stepwise/sleep-reflection.json. The revise_proposals step reads it back as the night's analysis input.
Data Sources the reflect Step Reads
Per flows/sleep/prompts/reflect.md, the reflect agent loads:
| Category | Sources |
|---|---|
| Profile & identity | profile/identity.json, profile/communication.json, data/identity/observations.jsonl (last 10β20) |
| Health | data/garmin/ (last 7 days), plus a live sleep-risk assessment and an implicit-checkin generator |
| Goals | goals/active/*.json + training-occurrence progress (via scripts.scheduling) |
| Session memory | data/assistant/{capture.json,commitments.json,learnings.jsonl} |
| Stream captures | data/stream-captures/* (next-session questions + today's captures) |
| Board | action items via scripts.board.action_items.scope_for_sleep |
| Working notes | data/scratch/{daily,weekly}.md |
| Memory | memory/views/ listing + staleness |
| FLL | the Feedback Learning Loop via scripts.proactive.fll_learning (engagement trends, sleep summary) |
| Calendar | data/calendar/events.json (last 7 / next 3 days) for travel cross-reference |
Note:
reflectis told not to readplans/proposals/*.mddirectly β therevise_proposalsstep handles proposals via the staging index. Older docs that listeddata/checkins/(30 days),data/briefs/*.json,meta/improvement-queue.json, anddata/proactive/conversations.jsonlare stale; the currentreflectstep does not read those.FLL vs VDB: "FLL" (Feedback Learning Loop,
scripts.proactive.fll_learning) is the current name. Older docs call this "VDB feedback" β prefer FLL.
Proposal Revision Mechanics
revise_proposals reads proposal-index.json, then revises 5β10 proposals in place. Two behaviors matter for a reimplementer:
skip_revision: Proposals with a terminal status, or 5+ consecutive revision sections recommending archive, are flaggedskip_revision: truebypreprocess.pyand skipped. Theauto_archive_triggeredlist records proposals auto-archived at the 5-consecutive-archive-recommendation threshold.- Revision-append corruption guard: Each revision is appended as a
## Sleep Cycle Revision (YYYY-MM-DD)block. It must be raw markdown with real newlines β ajson.dumps()-escaped one-liner is invisible to thepreprocess.pyparser (which requires a real newline after the date) and silently drops the revision fromlast_revision_dateand the archive-recommendation count. If corrupted blocks appear,scripts/sleep/repair_corrupted_revisions.pydecodes them back to markdown.
Significance Threshold (Conditional Generation)
In revise_proposals (Phase 6), new proposals are only generated if 2+ of these criteria are met:
- New data source added since last
/sleep - Goal completed or abandoned
- 3+ related captures suggesting unmet need
- 3+ learnings integrated into CLAUDE.md/skills
- FLL feedback pattern with no matching proposal
- Identity observation contradicting existing proposals
- Commitment repeatedly failed (systemic issue)
- Telegram pattern without a matching view
- View with 0 access (consider replacement)
- Board meeting feedback pattern
- Objective chronically deferred (3+ times)
If the threshold isn't met, generation is skipped and the step reports "No significant new patterns detected."
Immune-System Antibodies
/sleep runs deterministic self-audits that exist because shipped mitigations have died silently in the past. They are report-only β they write staging JSON and never auto-edit the files they audit; acting on the findings is the reflect agent's job.
| Antibody | Script | Output | Detects |
|---|---|---|---|
| Identity-state staleness | scripts/executive/detect_stale_systemic_issues.py |
data/sleep/staging/identity-state-staleness.json |
Systemic-issues rows contradicted by shipped proposals; dated section headers >14d old; dead file pointers in CLAUDE.md/ORIENTATION.md; ambient byte-budget bloat; and a semantic-claim grader that checks prose against its live primary source. A live-check exception now emits semantic_unverified; only the benign no-source-data case stays silent. |
| Feature-intent gap watch | scripts/sleep/feature_intent_gaps.py |
data/sleep/staging/feature-intent-gaps.json |
Feature commits since the last FEATURE_INTENT.md update β any commit whose <area>: subject prefix is not a maintenance verb (fix/chore/docs/β¦) and that touches a real feature surface (scripts/, flows/, api/, web/, ui/, rails/, .claude/skills/) |
| Completion-claim audit | scripts/audit/completion_claim_audit.py |
data/sleep/staging/completion-claim-audit.json |
Recent exec-loop βdone/verified/durableβ claims without same-cycle primary-source backing. Hedge markers such as βbehaviorally unverifiedβ or βout of scopeβ are annotated only as advisory context; they never remove a candidate or lower the unbacked count. |
Both are invoked by scripts/sleep/preprocess.py. They can also be run standalone:
python scripts/executive/detect_stale_systemic_issues.py
python scripts/sleep/feature_intent_gaps.py
Example: Agent Conversation Flow
Here's a real example of the comment-triggered agent model in action:
User posts comment:
"hit me up on telegram with these questions"
Agent spawns, reads proposal, searches codebase for Telegram integration, then responds:
Acknowledged! I've updated the proposal to use Telegram as the primary outreach
channel. Key changes:
- Replaced in-app notifications with Telegram messages
- Added schedule for morning/evening prompts
- Integrated with existing proactive queue system
The proposal now aligns with VITA's existing Telegram infrastructure.
User follows up:
"what about rate limiting so it's not annoying?"
Previous agent is killed, new agent spawns, addresses the new feedback:
Good point! Added rate limiting section:
- Max 3 prompts per day
- Snooze option per prompt type
- Smart backoff if user ignores 2+ in a row
- User can set quiet hours in preferences
Also added these as success metrics to track.
This back-and-forth continues until the proposal is refined enough to move to "ready" lane.
Quality Checklist for Proposals
Good proposals:
- Solve a real problem - Not a solution looking for a problem
- Fit the VITA philosophy - Proactive, data-centric, self-improving
- Are scoped - Clear boundaries, not trying to do everything
- Have measurable outcomes - Can verify if it worked
- Consider alternatives - Shows you've thought through options
- Identify risks honestly - Not overly optimistic
- Are actionable - Could start implementing tomorrow
Where to go next
- Executive Loop β the OODA loop that
/sleepreplaced the old_run_sleeporchestration from - Dev Tooling β Stepwise, the flow runner behind
flows/sleep/FLOW.yaml - Homeostasis β the prediction/equilibrium metrics surfaced in
data/sleep/staging/homeostasis.json - Model Guidance β frontier model roster and routing for the agent steps
meta/FEATURE_INTENT.md (in the repo) is the aspirational-direction document audited by the feature-intent gap watch.