Skip to content

Latest commit

Β 

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ“ Agentic ToDo

A multi-user TODO app fronted by an agentic AI assistant. From one chatbox it does CRUD on your todos, schedules reminders that later fire themselves, and remembers your preferences β€” every action reflected live in the dashboard.

Python Django DRF React Anthropic Tests


🎬 See it in action

Todo CRUD via chat
Todo CRUD via chat
Reminder CRUD via chat
Reminder CRUD via chat
Memory CRUD via chat
Memory CRUD via chat
Many actions in one agent loop
Many actions in one loop
Memory shapes later actions
Memory shapes actions
Reminder fires and notifies
Reminder fires β†’ notification

πŸ€– The agent

Agentic system design: prompt assembly, error-recovery-wrapped LLM, tool catalog, persisted messages, and per-user memory

One tool-calling loop (agent/runner.py) powers both chat turns and fired cron jobs β€” the chat endpoint and the run_scheduler command share the same tools, prompt assembly, and recovery. Each turn maps to the diagram above:

  • Dynamic system prompt β€” reassembled each turn from live state: identity, the auto-generated tool catalog (from the schemas, never hand-maintained), current time, todo stats, and the user's injected memory. β†’ prompt.py
  • LLM call with recovery β€” 429 backoff w/ jitter, 529 β†’ fallback model, max_tokens escalation + continuation, one prompt-too-long trim, graceful no-key degradation. β†’ llm.py
  • Caller-scoped tools β€” a fixed 12-tool catalog; each handler is a closure over request.user, so the model gets no id it can override and any cross-user id resolves to a benign "not found". β†’ tools.py
  • Secondary-LLM memory retrieval β€” once per turn a cheaper Claude model picks the stored facts relevant to the message and injects only those; at/below AGENT_MEMORY_RETRIEVAL_THRESHOLD facts it injects all (no extra call), and any failure falls back to all. β†’ memory.py
  • Auto-compaction β€” before each call, deterministic passes (no summariser LLM) shrink the in-flight context: an oversized tool result is truncated, older tool outputs collapse to a placeholder, then past a char budget the oldest turns are dropped at a safe user-prompt boundary. Only the in-flight context shrinks; the stored transcript stays complete. β†’ compaction.py
  • Persisted Messages[] β€” the transcript seeds context across turns; per-user memory is upserted by key. β†’ models.py
Agentic feature What it does
Chat β†’ action Natural-language CRUD on todos via the tool loop; the chat shows each tool call it made.
Agentic cron schedule_cron persists a 5-field job; when it fires, a real agent turn runs for that user and calls notify_user.
Per-user memory remember / recall durable facts, injected into every system prompt (length-capped).
Reactive UI Agent changes flash yellow, then apply: todos/reminders/memory rows highlight first, then create / update / fade.
Notifications Header bell popover + bottom-left toasts, polled every ~10 s.
Hardened Bounded tool-loop turns; isolation enforced structurally; the API key is never logged or returned.

Base app

A multi-user TODO list where each user manages a private set of items, JWT-protected and SQLite-backed. The assistant is optional: with no ANTHROPIC_API_KEY set, chat returns 503 and the scheduler idles; todos, auth, and the rest of the app keep working.

Backend Django + DRF + SimpleJWT
Frontend React 18 + Vite (vanilla fetch, plain CSS, no new deps)
Database SQLite
Auth JWT access tokens (30 min), Authorization: Bearer <token>
AI Anthropic Claude (tool use); model configurable by env

Core boundaries: registration with salted PBKDF2 passwords; every /api/ request gated by a valid JWT (401 otherwise); CRUD with structured 400 validation; per-user isolation via get_queryset (another user's row β†’ 404, never leaking existence); paginated lists.

Architecture

flowchart LR
    UI[React SPA Β· Vite :5173<br/>Dashboard + ChatWidget]
    subgraph Server[Django :8000]
        AUTH[accounts Β· register + token]
        TODO[todos Β· TodoViewSet]
        AGENT[agent Β· chat Β· memories Β· jobs Β· notifications]
        RUNNER[[agent runner Β· tool loop]]
        SCHED[run_scheduler Β· cron poller]
    end
    DB[(SQLite)]
    LLM([Anthropic Claude])

    UI -- "Bearer Β· CRUD /api/todos/" --> TODO
    UI -- "POST /api/chat/messages/ Β· poll" --> AGENT
    UI -- "register / token" --> AUTH
    AGENT --> RUNNER
    SCHED -- "fired job β†’ agent turn" --> RUNNER
    RUNNER -- "tool use" --> LLM
    AUTH --- DB
    TODO --- DB
    AGENT --- DB
    RUNNER --- DB
Loading

The SPA keeps the JWT in localStorage; a 401 clears it and returns to login. The same request.user scoping from Core boundaries holds on the server for the agent's tools too, so ownership is enforced regardless of what the client or the model asks for.

Quick Start

Prerequisites: Python 3.9+, Node 18+.

# 1. Backend β†’ http://localhost:8000
cd backend
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
cp .env.example .env          # set SECRET_KEY; add ANTHROPIC_API_KEY to enable the assistant
.venv/bin/python manage.py migrate
.venv/bin/python manage.py runserver

# 2. Scheduler (second terminal): fires agentic cron jobs
.venv/bin/python manage.py run_scheduler

# 3. Frontend β†’ http://localhost:5173 (third terminal)
cd frontend && npm install && npm run dev

Register, then click the sparkles button (bottom-right) and try "add buy milk, then mark my oldest done", "remind me to stretch every hour", or "remember I prefer short titles". Watch the dashboard update live. Point the UI elsewhere with VITE_API_BASE in frontend/.env.

The scheduler polls every AGENT_SCHEDULER_INTERVALs (default 30) and matches cron at minute granularity; jobs fire only while it runs (no backfill).

Agent configuration (backend/.env)

Var Default Purpose
ANTHROPIC_API_KEY (blank) Enables the assistant. Blank β‡’ chat 503s, scheduler idles; rest of app works. Never logged or returned.
ANTHROPIC_MODEL claude-opus-4-8 Model used for tool-use chat.
ANTHROPIC_FALLBACK_MODEL claude-sonnet-4-6 Switched to after repeated 529/overloaded errors.
ANTHROPIC_SECONDARY_MODEL claude-haiku-4-5 Cheaper model for the per-turn memory-retrieval pass.
AGENT_MEMORY_RETRIEVAL_THRESHOLD 5 At/below this many facts, inject all and skip the retrieval call.
AGENT_SCHEDULER_INTERVAL 30 Scheduler poll cadence (seconds).
AGENT_MAX_TURNS 8 Tool-loop turn cap per turn (runaway guard).
AGENT_CONTEXT_CHAR_BUDGET 60000 Context size (JSON chars) past which rule-based auto-compaction trims.

API Reference

Method Endpoint Description
POST /api/auth/register Β· /api/auth/token Create a user / obtain a JWT pair (no auth)
GET POST PATCH DELETE /api/todos/ …/{id}/ CRUD on the user's todos (paginated)
GET /api/todos/stats/ {open, done, total} for the dashboard
GET POST /api/chat/messages/ Read transcript / send a message β†’ {messages, actions} (or 503 w/o a key)
POST /api/chat/messages/reset/ Clear the conversation
GET DELETE /api/memories/ …/{id}/ List / forget remembered facts
GET DELETE /api/scheduled-jobs/ …/{id}/ List / cancel reminders (with human schedule + next-fire labels)
GET PATCH DELETE /api/notifications/ …/{id}/ List / mark read / delete (+ mark-all-read/, clear/)

All /api/ routes except register/token require a bearer token. The agent's 12 tools run owner-scoped ORM ops and reuse TodoSerializer validation; they live in agent/tools.py and the system-prompt catalog is generated from their schemas.

Testing

cd backend && .venv/bin/python manage.py test     # Ran 77 tests: OK

A fake Anthropic client is injected throughout, so the suite is fully offline and deterministic β€” the real API is never called. It covers the base auth/CRUD/validation/ isolation suite plus the agent: cron validate/match (incl. day-of-month-or-day-of-week), a full chat turn, the no-key 503 path, tool + memory isolation (incl. prompt injection), scheduler firing (double-fire and one-shot guards), LLM recovery (prompt-too-long trim, max_tokens tool_use), and rule-based auto-compaction (non-mutating, with the stored transcript preserved).

Project Structure

backend/
β”œβ”€β”€ config/   # settings, urls (DRF + JWT + CORS + agent include)
β”œβ”€β”€ accounts/ # registration + SimpleJWT
β”œβ”€β”€ todos/    # Todo model, serializer, viewset (+ stats), tests
└── agent/    # AI agent app
    β”œβ”€β”€ models.py     # Conversation, ChatMessage, Memory, ScheduledJob, Notification
    β”œβ”€β”€ prompt.py     # dynamic system-prompt assembly (catalog from tools.py)
    β”œβ”€β”€ memory.py     # secondary-LLM memory retrieval (relevance pass + fallback)
    β”œβ”€β”€ compaction.py # rule-based context compaction (cap / collapse / boundary-safe trim)
    β”œβ”€β”€ tools.py      # TOOL_SCHEMAS + build_handlers(user) closure (isolation guardrail)
    β”œβ”€β”€ llm.py        # Anthropic client + RecoveryState + with_retry
    β”œβ”€β”€ runner.py     # run_agent_turn / run_cron_turn: the shared tool loop
    β”œβ”€β”€ cron.py       # pure 5-field cron validate / match / humanize
    β”œβ”€β”€ scheduler.py  # run_due_jobs: minute-granular firing w/ double-fire guard
    β”œβ”€β”€ views.py Β· serializers.py Β· urls.py
    β”œβ”€β”€ management/commands/run_scheduler.py
    └── tests.py      # offline suite (fake Anthropic client)
frontend/src/  # api.js, App, AuthView, Dashboard, ChatWidget, Header, Stats,
               # TodoList, SidePanels, Toasts, Icon, util, styles.css
ui-design/     # the provided design kit (tokens, mockup, reference JSX)
docs/          # prd/PRD.md Β· prd/PRD-ai-agent.md

Scope

Built against approved specs: PRD.md (base) and PRD-ai-agent.md (agent).

Out of scope: sharing/collaboration, due dates/tags, OAuth/refresh tokens, production hardening (HTTPS, rate limiting); for the agent: streaming, multi-conversation UI, RAG/vector memory, summarising-LLM compaction (the deterministic passes above cover it), and broker infra (Celery/Redis).

Known limitation: chat turns are synchronous and the scheduler is a single local poller β€” retry sleeps and the LLM call block the request worker thread. Fine for local single-user use; production would offload turns to a task queue.

License

MIT

About

Simple TODO app driven by an agentic AI assistant

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Contributors

Languages