13 KiB
13 KiB
Phase 4: Local AI, Automation & Ingestion Pipeline
Status: Pending Phase 3 Completion
Phase Goal: Implement the full local AI inference pipeline in chronogrid_ai, the IMAP ingestion engine, filesystem watchers, the Eisenhower score computation engine, the CalDAV embedded server, and the automation rules engine — all running entirely on-device with no cloud API calls.
Prerequisites: Phase 3 complete ✅
Estimated Complexity: Very High
Blocking: Phase 5 (final integration and release testing)
Task 4.1 — Eisenhower Priority Score Engine
- Task 4.1: Implement the Eisenhower priority score computation engine in
chronogrid_core:- Create
crates/chronogrid_core/src/scoring/mod.rs - Implement
EisenhowerWeightsstruct:w_importance: f64,w_urgency: f64,w_age: f64(must sum to 1.0) - Implement
urgency_decay(due_date_ms: u64, now_ms: u64) -> f64:- Exponential decay function:
f(Δt) = e^(-λ * Δt_hours)where λ = 0.05 - Returns 1.0 when due_date is now, approaches 0.0 for distant future dates
- Returns 1.5 (overdue boost) when due_date is in the past
- Exponential decay function:
- Implement
compute_priority_score(task: &TaskSnapshot, weights: &EisenhowerWeights, now_ms: u64) -> f64S = (w_i * p_user) + (w_u * urgency_decay(due_date, now)) + (w_a * age_factor)age_factor = min(1.0, days_since_created / 30.0)— tasks age into importance over 30 days
- Implement
assign_eisenhower_quadrant(score: f64, due_date: Option<u64>, now_ms: u64, threshold_high: f64) -> i64- Q1: score >= threshold AND due within 48 hours
- Q2: score >= threshold AND due > 48 hours or no due date
- Q3: score < threshold AND due within 48 hours
- Q4: score < threshold AND due > 48 hours or no due date
- Implement
Database::recompute_all_scores(weights: &EisenhowerWeights) -> Result<usize>— batch update all tasks - Add
scoringmodule tochronogrid_core/src/lib.rspublic API - Unit tests: decay function values, score computation, quadrant assignment, batch recompute
- Run
cargo test --package chronogrid_core
- Create
Task 4.2 — Implement chronogrid_ai: NLP Metadata Extractor
- Task 4.2: Implement the NLP metadata extraction engine in
crates/chronogrid_ai/:- Add dependencies:
regex = "1",chrono = { version = "0.4", features = ["serde"] },serde = { version = "1", features = ["derive"] },serde_json = "1" - Implement
nlp::DateTimeExtractor:- Parse natural language date/time expressions using regex patterns:
- Relative: "tomorrow", "next Monday", "in 3 days", "next week"
- Absolute: "August 25th", "2026-08-25", "Thursday at 2pm", "by 3 PM Friday"
- Duration: "for 2 hours", "30 minutes", "half an hour"
- Returns
ExtractedDateTime { due_date: Option<DateTime<Utc>>, duration_minutes: Option<i64> }
- Parse natural language date/time expressions using regex patterns:
- Implement
nlp::NamespaceClassifier:- Rule-based keyword classifier mapping text to namespace IDs
- Configurable keyword lists per namespace (loaded from
automation_rulestable) - Returns
ClassificationResult { namespace_id: String, confidence: f64 }
- Implement
nlp::TaskNoteSplitter:- Detects action items: lines starting with
- [ ],TODO:,ACTION:, imperative verbs - Separates actionable items (→ tasks) from reference text (→ notes)
- Returns
SplitResult { tasks: Vec<String>, note_content: String }
- Detects action items: lines starting with
- Implement
nlp::process_raw_input(text: &str, context: &ProcessingContext) -> ProcessedInput- Runs all three extractors in sequence
- Returns structured
ProcessedInputwith extracted tasks, note content, dates, namespace
- Unit tests: date extraction for all pattern types, namespace classification, task/note splitting
- Run
cargo test --package chronogrid_ai
- Add dependencies:
Task 4.3 — Implement chronogrid_ai: Local LLM Integration (Ollama)
- Task 4.3: Implement optional local LLM inference via Ollama HTTP socket in
crates/chronogrid_ai/:- Add dependencies:
reqwest = { version = "0.12", features = ["json", "blocking"] },serde_json = "1" - Implement
llm::OllamaClient:OllamaClient::new(base_url: &str) -> Self— defaults tohttp://127.0.0.1:11434OllamaClient::is_available() -> bool— health check pingOllamaClient::complete(model: &str, prompt: &str) -> Result<String>— blocking inference call
- Implement
llm::TaskEnrichmentPrompt:- Structured prompt template for task metadata extraction
- Input: raw text, Output: JSON with
{ title, description, due_date, duration_minutes, namespace, priority_hint }
- Implement
llm::enrich_task_with_llm(text: &str, client: &OllamaClient) -> Result<EnrichedTask>- Falls back to regex NLP extractor if Ollama is unavailable
- Validates LLM JSON output against expected schema
- Feature flag:
features = ["ollama"]— Ollama integration is opt-in - Unit tests: prompt construction, JSON response parsing, fallback behavior when Ollama unavailable
- Run
cargo test --package chronogrid_ai
- Add dependencies:
Task 4.4 — Implement IMAP Ingestion Engine
- Task 4.4: Implement the direct TLS IMAP polling engine in
chronogrid_core:- Add dependencies:
async-imap = "0.9",async-native-tls = "0.5",tokio = { version = "1", features = ["full"] },mail-parser = "0.9" - Create
crates/chronogrid_core/src/ingestion/imap.rs:- Implement
ImapConfigstruct:host,port,username,password_keychain_key,mailbox,poll_interval_secs,namespace_id - Implement
ImapPoller:ImapPoller::new(config: ImapConfig, db: Arc<Mutex<Database>>) -> SelfImapPoller::poll_once() -> Result<Vec<IngestionResult>>— connects, fetches unseen messages, disconnectsImapPoller::run_background(shutdown: CancellationToken)— Tokio task that polls on interval
- Email processing pipeline:
- Fetch unseen messages from configured mailbox
- Extract:
message_id,subject,body_text,attachments,date - Convert body to Markdown (strip HTML, preserve structure)
- Save attachments as encrypted blobs in
attachmentstable - Run
chronogrid_ai::nlp::process_raw_inputon subject + body - Create task CRDT document with extracted metadata
- Log to
ingestion_logstable withsource_type = 'imap' - Mark message as seen / move to processed folder
- Implement
- Unit tests: email parsing, Markdown conversion, attachment handling, ingestion log creation
- Integration test: mock IMAP server (using
greenmailor in-process mock) → full pipeline → task created in DB - Run
cargo test --package chronogrid_core
- Add dependencies:
Task 4.5 — Implement Filesystem Watcher
- Task 4.5: Implement the local filesystem watcher in
chronogrid_core:- Add dependency:
notify = "6"(cross-platform filesystem events) - Create
crates/chronogrid_core/src/ingestion/file_watcher.rs:- Implement
WatchConfigstruct:watch_path: PathBuf,namespace_id: String,file_extensions: Vec<String> - Implement
FileWatcher:FileWatcher::new(config: WatchConfig, db: Arc<Mutex<Database>>) -> Result<Self>FileWatcher::start(shutdown: CancellationToken)— Tokio task watching forCreateandModifyevents
- File processing pipeline:
- On file event: read file content
- Parse Markdown
- [ ]checkboxes as task candidates - Run
chronogrid_ai::nlp::process_raw_inputon file content - Create/update note CRDT document with file content
- Extract tasks from
- [ ]items and create task CRDT documents - Log to
ingestion_logswithsource_type = 'file_watcher'
- Implement
- Unit tests: file event handling, Markdown task extraction, note creation
- Run
cargo test --package chronogrid_core
- Add dependency:
Task 4.6 — Implement Global Hotkey Jot Capture (Desktop)
- Task 4.6: Implement the global hotkey quick-jot capture system:
- Add dependency to
chronogrid_ffi:global-hotkey = "0.5"(cross-platform global hotkeys) - Implement
api::jot::register_global_hotkey(hotkey: String) -> anyhow::Result<()>— registers system-wide hotkey - Implement
api::jot::submit_jot(text: String) -> anyhow::Result<FfiTaskData>— processes raw jot text through AI pipeline and creates task - Flutter side: implement
JotOverlay— lightweight floating window that appears on hotkey press:- Single-line text input with submit button
- Appears as a system overlay (Windows:
flutter_acrylicfor frosted glass effect) - On submit: calls
submitJot()FFI function, shows confirmation toast, dismisses
- Android: implement
QuickJotWidget— home screen widget usinghome_widgetFlutter package - Unit tests: hotkey registration, jot processing pipeline
- Run
cargo test --package chronogrid_ffi
- Add dependency to
Task 4.7 — Implement CalDAV Embedded Server
- Task 4.7: Implement the embedded CalDAV server in
chronogrid_core:- Add dependencies:
axum = "0.7",tokio = { version = "1", features = ["full"] },quick-xml = "0.36" - Create
crates/chronogrid_core/src/caldav/mod.rs:- Implement
CalDavServer:CalDavServer::new(db: Arc<Mutex<Database>>, bind_addr: SocketAddr) -> SelfCalDavServer::start(shutdown: CancellationToken)— starts Axum HTTP server on127.0.0.1:4242
- Implement CalDAV endpoints:
PROPFIND /— calendar discoveryPROPFIND /calendars/{namespace_id}/— calendar collection propertiesREPORT /calendars/{namespace_id}/— calendar query (returns iCalendar events for time-blocked tasks)PUT /calendars/{namespace_id}/{task_id}.ics— create/update time block from external calendarDELETE /calendars/{namespace_id}/{task_id}.ics— remove time block
- iCalendar (RFC 5545) serialization: convert
time_blocks+taskstoVEVENTformat - iCalendar parsing: convert incoming
VEVENTtotime_blocksentries
- Implement
- Unit tests: iCalendar serialization, PROPFIND response XML, REPORT query filtering
- Integration test: CalDAV client (using
vdirsyncerorcurl) syncs with embedded server - Run
cargo test --package chronogrid_core
- Add dependencies:
Task 4.8 — Implement Automation Rules Engine
- Task 4.8: Implement the local automation rules engine in
chronogrid_core:- Create
crates/chronogrid_core/src/automation/mod.rs:- Implement
AutomationRulestruct:trigger: Trigger,conditions: Vec<Condition>,actions: Vec<Action> - Implement
Triggerenum:OnIngestion { source_type },OnTaskCreated,OnTaskStatusChanged,OnSchedule { cron_expr } - Implement
Conditionenum:NamespaceMatches { id },TitleContains { keyword },PriorityAbove { threshold },DueDateWithin { hours } - Implement
Actionenum:SetNamespace { id },SetProject { id },SetPriority { score },AddTag { tag },SendToCalendar,ArchiveTask - Implement
RulesEngine:RulesEngine::load_rules(db: &Database) -> Result<Self>— loads rules fromautomation_rulestableRulesEngine::evaluate(event: &AutomationEvent, db: &mut Database) -> Result<Vec<ActionResult>>
- Implement
- Expose rules management via FFI:
create_automation_rule,list_automation_rules,delete_automation_rule - Unit tests: rule evaluation, condition matching, action execution, rule persistence
- Run
cargo test --package chronogrid_core
- Create
Task 4.9 — Wire AI Pipeline into FFI & Flutter UI
- Task 4.9: Expose the AI/automation pipeline through the FFI layer and integrate into Flutter:
- Add
chronogrid_aias a dependency inchronogrid_ffi/Cargo.toml - Implement FFI functions in
api/ingestion.rs:process_jot_text(text: String) -> anyhow::Result<FfiTaskData>— full AI pipeline on raw textconfigure_imap_account(config_json: String) -> anyhow::Result<()>— stores IMAP configstart_imap_polling() -> anyhow::Result<()>— starts background IMAP pollerstop_imap_polling() -> anyhow::Result<()>configure_file_watcher(path: String, namespace_id: String) -> anyhow::Result<()>get_ingestion_logs(limit: u32) -> anyhow::Result<Vec<FfiIngestionLog>>
- Re-run
flutter_rust_bridge_codegen generateto update Dart bindings - Implement
IngestionBlocin Flutter with events:StartImapPolling,StopImapPolling,ProcessJot,LoadIngestionLogs - Add ingestion status panel to
SettingsScreen - Unit tests: FFI ingestion functions,
IngestionBlocstate transitions - Run
cargo test --package chronogrid_ffiandflutter test
- Add
Phase 4 Completion Criteria
- All 9 tasks above marked
[x] cargo test --workspacepasses with zero failures- IMAP polling creates tasks in the database from test email messages
- File watcher creates notes and tasks from Markdown files dropped in watch folder
- Eisenhower scores recompute correctly for all test task scenarios
- CalDAV server responds to
PROPFINDandREPORTrequests from a CalDAV client - Global hotkey jot capture creates a task within 2 seconds (acceptance criterion from user story)
scripts/verify_agent_work.ps1exits with code 0- ADR created for AI/automation decisions (
docs/ARCHITECTURE/ADR/0008-local-ai-automation-pipeline.md) DECISION_LOG.mdupdated with Phase 4 decisions