Add tracks — auto-updating note blocks with scheduled and event-driven triggers
Track blocks are YAML-fenced sections embedded in markdown notes whose output
is rewritten by a background agent. Three trigger types: manual (Run button or
Copilot), scheduled (cron / window / once with a 2 min grace window), and
event-driven (Gmail/Calendar sync events routed via an LLM classifier with a
second-pass agent decision). Output lives between <!--track-target:ID-->
comment markers that render as editable content in the Tiptap editor so users
can read and extend AI-generated content inline.
Core:
- Schedule and event pipelines run as independent polling loops (15s / 5s),
both calling the same triggerTrackUpdate orchestrator. Events are FIFO via
monotonic IDs; a per-track Set guards against duplicate runs.
- Track-run agent builds three message variants (manual/timed/event) — the
event variant includes a Pass 2 directive to skip updates on false positives
flagged by the liberal Pass 1 router.
- IPC surface: track:run/get/update/replaceYaml/delete plus tracks:events
forward of the pub-sub bus to the renderer.
- Gmail emits per-thread events; Calendar bundles a digest per sync.
Copilot:
- New `tracks` skill (auto-generated canonical schema from Zod via
z.toJSONSchema) teaches block creation, editing, and proactive suggestion.
- `run-track-block` tool with optional `context` parameter for backfills
(e.g. seeding a new email-tracking block from existing synced emails).
Renderer:
- Tiptap chip (display-only) opens a rich modal with tabs, toggle, schedule
details, raw YAML editor, and confirm-to-delete. All mutations go through
IPC so the backend stays the single writer.
- Target regions use two atom marker nodes (open/close) around real editable
content — custom blocks render natively, users can add their own notes.
- "Edit with Copilot" seeds a chat session with the note attached.
Docs: apps/x/TRACKS.md covers product flows, technical pipeline, and a
catalog of every LLM prompt involved with file+line pointers.
2026-04-14 13:51:45 +05:30
import z from 'zod' ;
export const TrackScheduleSchema = z . discriminatedUnion ( 'type' , [
z . object ( {
type : z . literal ( 'cron' ) . describe ( 'Fires at exact cron times' ) ,
expression : z.string ( ) . describe ( '5-field cron expression, quoted (e.g. "0 * * * *")' ) ,
} ) . describe ( 'Recurring at exact times' ) ,
z . object ( {
type : z . literal ( 'window' ) . describe ( 'Fires at most once per cron occurrence, only within a time-of-day window' ) ,
cron : z.string ( ) . describe ( '5-field cron expression, quoted' ) ,
startTime : z.string ( ) . regex ( /^([01]\d|2[0-3]):[0-5]\d$/ ) . describe ( '24h HH:MM, local time' ) ,
endTime : z.string ( ) . regex ( /^([01]\d|2[0-3]):[0-5]\d$/ ) . describe ( '24h HH:MM, local time' ) ,
} ) . describe ( 'Recurring within a time-of-day window' ) ,
z . object ( {
type : z . literal ( 'once' ) . describe ( 'Fires once and never again' ) ,
runAt : z.string ( ) . describe ( 'ISO 8601 datetime, local time, no Z suffix (e.g. "2026-04-14T09:00:00")' ) ,
} ) . describe ( 'One-shot future run' ) ,
] ) . describe ( 'Optional schedule. Omit entirely for manual-only tracks.' ) ;
export type TrackSchedule = z . infer < typeof TrackScheduleSchema > ;
export const TrackBlockSchema = z . object ( {
trackId : z.string ( ) . regex ( /^[a-z0-9]+(-[a-z0-9]+)*$/ ) . describe ( 'Kebab-case identifier, unique within the note file' ) ,
instruction : z.string ( ) . min ( 1 ) . describe ( 'What the agent should produce each run — specific, single-focus, imperative' ) ,
eventMatchCriteria : z.string ( ) . optional ( ) . describe ( 'When set, this track participates in event-based triggering. Describe what kinds of events should consider this track for an update (e.g. "Emails about Q3 planning"). Omit to disable event triggers — the track will only run on schedule or manually.' ) ,
active : z.boolean ( ) . default ( true ) . describe ( 'Set false to pause without deleting' ) ,
schedule : TrackScheduleSchema.optional ( ) ,
2026-04-24 16:58:18 +05:30
model : z.string ( ) . optional ( ) . describe ( 'ADVANCED — leave unset. Per-track LLM model override (e.g. "anthropic/claude-sonnet-4.6"). Only set when the user explicitly asked for a specific model for THIS track. The global default already picks a tuned model for tracks; overriding usually makes things worse, not better.' ) ,
provider : z.string ( ) . optional ( ) . describe ( 'ADVANCED — leave unset. Per-track provider name override (e.g. "openai", "anthropic"). Only set when the user explicitly asked for a specific provider for THIS track. Almost always omitted; the global default flows through correctly.' ) ,
Add tracks — auto-updating note blocks with scheduled and event-driven triggers
Track blocks are YAML-fenced sections embedded in markdown notes whose output
is rewritten by a background agent. Three trigger types: manual (Run button or
Copilot), scheduled (cron / window / once with a 2 min grace window), and
event-driven (Gmail/Calendar sync events routed via an LLM classifier with a
second-pass agent decision). Output lives between <!--track-target:ID-->
comment markers that render as editable content in the Tiptap editor so users
can read and extend AI-generated content inline.
Core:
- Schedule and event pipelines run as independent polling loops (15s / 5s),
both calling the same triggerTrackUpdate orchestrator. Events are FIFO via
monotonic IDs; a per-track Set guards against duplicate runs.
- Track-run agent builds three message variants (manual/timed/event) — the
event variant includes a Pass 2 directive to skip updates on false positives
flagged by the liberal Pass 1 router.
- IPC surface: track:run/get/update/replaceYaml/delete plus tracks:events
forward of the pub-sub bus to the renderer.
- Gmail emits per-thread events; Calendar bundles a digest per sync.
Copilot:
- New `tracks` skill (auto-generated canonical schema from Zod via
z.toJSONSchema) teaches block creation, editing, and proactive suggestion.
- `run-track-block` tool with optional `context` parameter for backfills
(e.g. seeding a new email-tracking block from existing synced emails).
Renderer:
- Tiptap chip (display-only) opens a rich modal with tabs, toggle, schedule
details, raw YAML editor, and confirm-to-delete. All mutations go through
IPC so the backend stays the single writer.
- Target regions use two atom marker nodes (open/close) around real editable
content — custom blocks render natively, users can add their own notes.
- "Edit with Copilot" seeds a chat session with the note attached.
Docs: apps/x/TRACKS.md covers product flows, technical pipeline, and a
catalog of every LLM prompt involved with file+line pointers.
2026-04-14 13:51:45 +05:30
lastRunAt : z.string ( ) . optional ( ) . describe ( 'Runtime-managed — never write this yourself' ) ,
lastRunId : z.string ( ) . optional ( ) . describe ( 'Runtime-managed — never write this yourself' ) ,
lastRunSummary : z.string ( ) . optional ( ) . describe ( 'Runtime-managed — never write this yourself' ) ,
} ) ;
// ---------------------------------------------------------------------------
// Knowledge events (event-driven track triggering pipeline)
// ---------------------------------------------------------------------------
export const KnowledgeEventSchema = z . object ( {
id : z.string ( ) . describe ( 'Monotonically increasing ID; also the filename in events/pending/' ) ,
source : z.string ( ) . describe ( 'Producer of the event (e.g. "gmail", "calendar")' ) ,
type : z . string ( ) . describe ( 'Event type (e.g. "email.synced")' ) ,
createdAt : z.string ( ) . describe ( 'ISO timestamp when the event was produced' ) ,
payload : z.string ( ) . describe ( 'Human-readable event body, usually markdown' ) ,
targetTrackId : z.string ( ) . optional ( ) . describe ( 'If set, skip routing and target this track directly (used for re-runs)' ) ,
targetFilePath : z.string ( ) . optional ( ) ,
// Enriched on move from pending/ to done/
processedAt : z.string ( ) . optional ( ) ,
candidates : z.array ( z . object ( {
trackId : z.string ( ) ,
filePath : z.string ( ) ,
} ) ) . optional ( ) ,
runIds : z.array ( z . string ( ) ) . optional ( ) ,
error : z.string ( ) . optional ( ) ,
} ) ;
export type KnowledgeEvent = z . infer < typeof KnowledgeEventSchema > ;
export const Pass1OutputSchema = z . object ( {
candidates : z.array ( z . object ( {
trackId : z.string ( ) . describe ( 'The track block identifier' ) ,
filePath : z.string ( ) . describe ( 'The note file path the track lives in' ) ,
} ) ) . describe ( 'Tracks that may be relevant to this event. trackIds are only unique within a file, so always return both fields.' ) ,
} ) ;
export type Pass1Output = z . infer < typeof Pass1OutputSchema > ;
// Track bus events
export const TrackRunStartEvent = z . object ( {
type : z . literal ( 'track_run_start' ) ,
trackId : z.string ( ) ,
filePath : z.string ( ) ,
trigger : z.enum ( [ 'timed' , 'manual' , 'event' ] ) ,
runId : z.string ( ) ,
} ) ;
export const TrackRunCompleteEvent = z . object ( {
type : z . literal ( 'track_run_complete' ) ,
trackId : z.string ( ) ,
filePath : z.string ( ) ,
runId : z.string ( ) ,
error : z.string ( ) . optional ( ) ,
summary : z.string ( ) . optional ( ) ,
} ) ;
export const TrackEvent = z . union ( [ TrackRunStartEvent , TrackRunCompleteEvent ] ) ;
export type TrackBlock = z . infer < typeof TrackBlockSchema > ;
export type TrackEventType = z . infer < typeof TrackEvent > ;