~/projects/TaskIQ · README.md
TaskIQ — AI Project Manager
Turns unstructured meeting notes and chat logs into structured, prioritized tasks — a chain-of-thought Gemini pipeline with schema-constrained JSON extraction, a persistence layer, and a live drag-and-drop Kanban board on top.
01 · overview
The problem
Meeting notes and chat logs are full of tasks that never make it onto anyone's board — buried in a sentence, missing an owner, with a deadline stated as "by EOD" instead of a date. Someone has to read the whole thing and manually translate it into tickets. TaskIQ does that translation step: paste in raw, messy text and get back structured tasks — description, owner, due date, priority — ready to drop onto a Kanban board.
The core design problem is reliability of extraction. A plain "summarize this into tasks" prompt tends to invent fields, skip implicit deadlines, or return inconsistent JSON shapes between calls. TaskIQ constrains the model with a Pydantic response schema and forces it to reason before it extracts, rather than trusting free-form output.
02 · architecture
System architecture
A vanilla JS/HTML/CSS frontend talking to a small FastAPI service, backed by SQLite and Google's Gemini API.
or chat log
schema-locked JSON
due date, priority
status columns
The schema's first field is reasoning, not the task itself — the model has to write out its inference about priority and ownership before it's allowed to produce the structured fields.
Every LLM call is wrapped in Tenacity's retry with exponential backoff (up to 3 attempts), so a transient API hiccup doesn't surface as a failed extraction.
A second, independent flow reads every task row back out of SQLite and asks Gemini for a stakeholder-ready status summary — on-track / at-risk / behind, highlights, blockers, next steps.
03 · how it works
How extraction works
Schema-constrained output, not free-form JSON
Extraction targets a Pydantic model — reasoning, description, due_date, owner, priority — passed to the Gemini API as a native response_schema with response_mime_type: application/json. The model literally cannot return a shape other than the one TaskIQ expects; there's no regex-and-hope JSON parsing on the way back.
Reasoning before extraction
reasoning is deliberately the first field in the schema. Because Gemini's structured output is generated field-by-field in order, putting reasoning first forces the model to think through who owns the task and how urgent it is before it commits to the priority and owner values — a lightweight chain-of-thought trick that costs one extra field and meaningfully improves consistency.
A one-shot example baked into the prompt
The system prompt includes one worked example directly, anchoring the model's sense of "correct" before it ever sees real input — the same example shown in the walkthrough below. At temperature = 0.1, generation stays close to that anchor rather than drifting stylistically between calls.
Resilience via exponential backoff
Both the extraction call and the summary call are wrapped in Tenacity's @retry decorator: up to 3 attempts, exponential wait starting at 2 seconds and capping at 10. A transient rate limit or network blip retries silently instead of surfacing as a broken request.
Persistence & the AI summary
Extracted tasks land in a SQLite tasks table (id, description, due_date, owner, priority, status). A separate /api/tasks/summary endpoint re-reads the full task list and asks Gemini — at a looser temperature = 0.3, since this call is prose rather than structured data — for a markdown status report capped at 200 words: overall status, key highlights, blockers, and next steps.
04 · worked example
The example baked into the prompt
This exact input/output pair is embedded in llm.py as the model's one-shot anchor — reproduced here verbatim from the source.
"We need to fix the login bug immediately, it's crashing production. Sarah, please handle this by EOD."
POST /api/tasks/extract
{
"reasoning": "The login bug is crashing production, which indicates
critical urgency. Sarah is explicitly assigned. Deadline is EOD.",
"description": "Fix production crash caused by login bug",
"due_date": "Today",
"owner": "Sarah",
"priority": "High"
}
Notice the order: reasoning is written first and does the actual work — it names the urgency signal, the assignee, and the deadline before those become structured fields. priority: "High" isn't a keyword match on "immediately"; it's downstream of the model already having reasoned that a production outage is urgent.
05 · kanban & reliability
From extraction to board
— Kanban board
- Native HTML5 drag-and-drop across To Do / In Progress / Done columns — dragging a card fires a
PUT /api/tasks/{task_id}to persist the new status immediately. - CSV export of the current task list for sharing outside the app.
- Automatic dark mode via
prefers-color-scheme, matching the user's OS setting with no manual toggle needed. - One-click AI summary panel that calls
/api/tasks/summaryand renders the markdown report inline.
— Test suite
- Isolated per-test database — a fixture creates a fresh SQLite file before each test and deletes it after, so tests never share state.
- Full CRUD coverage — create, list, update, and delete are each exercised through FastAPI's
TestClientagainst real HTTP routes. - Mocked LLM calls — the extraction test monkeypatches
llm.extract_tasks_from_text, so the suite runs fast, free, and deterministically without calling the real Gemini API. - Static route smoke test — confirms the frontend's
index.htmlis actually served at/.
06 · tech stack
Built with
| Method | Endpoint | Purpose |
|---|---|---|
| POST | /api/tasks/extract | Extract structured tasks from raw notes |
| GET | /api/tasks | List all tasks |
| GET | /api/tasks/summary | Generate the AI executive summary |
| GET | /api/tasks/{task_id} | Get a single task |
| PUT | /api/tasks/{task_id} | Update a task (e.g. on Kanban drag) |
| DELETE | /api/tasks/{task_id} | Delete a task |