why-agent / README.md
MapoTofu9's picture
deploy: HF Spaces
5d30bdc
|
Raw
History Blame Contribute Delete
29.8 kB
metadata
title: why-agent
emoji: πŸ”
colorFrom: indigo
colorTo: purple
sdk: docker
app_port: 7860
pinned: false
license: mit

why-agent β€” internal working doc

Owners: Mapo, Isa

This is our working doc. It's where we record what we agreed on, why we agreed on it, and who owns what. Update it when decisions change.


1. What we're building

An autonomous root-cause agent for data. User asks "why did metric X move?" β€” agent investigates and returns a structured report with an evidence chain. Works against any user-provided DuckDB/Parquet dataset. Built on AMD MI300X, deployed to Streamlit Community Cloud.

Working name: Why Agent. Repo name: why-agent.


2. Why this, not something else

We considered three other directions and rejected them. The reasoning matters β€” it's our defense if we're tempted to scope-creep later.

Considered Why we rejected it
Multi-agent research over arXiv Crowded space (Undermind, Elicit, Consensus). No clear users beyond PhD niche.
Conflict-aware research agent Intellectually interesting, no real buyer. "Neat" β‰  "needed."
Generic NL-to-SQL agent Saturated. Every cloud and BI vendor ships one. We can't out-product Snowflake.
Multi-agent diagnostic system One capable agent teaches us more about agent fundamentals than orchestrating several shallow ones.

What makes why-agent the right pick:

  • Real users: any data team at any company > a few hundred people.
  • Real gap: only Tellius does autonomous RCA, and it's closed enterprise. No OSS equivalent.
  • Plays to us: Isa lives this problem at PayPal; Mapo has shipped RAG + agentic systems.
  • Beyond simple RAG: required by the hackathon brief, naturally true here.

3. Business insight (the thing this fixes)

Every company runs on metrics. When a metric moves unexpectedly, an analyst spends 30–90 minutes doing mechanical work:

Confirm the drop is real Decompose by every dimension they can think of Find the anomalous slice Drill in further Cross-check related signals Write up the conclusion

This is expert-level but repetitive. It's exactly the shape of work agents do well. But today's tools answer "what is X?", not "why did X change?".

The market split:

  • Descriptive layer (NL-to-SQL): saturated. Snowflake Cortex, Databricks Genie, Hex, Julius, Looker+Gemini, etc.
  • Diagnostic layer (autonomous RCA): one closed-source vendor (Tellius). Nothing in OSS. This is where we play.

Our wedge in one sentence:

What Looker shows you, why-agent investigates for you.


4. Architecture overview

Judge / user                            Cost
β”‚                                  ────
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        $0
β”‚ Streamlit Cloud              β”‚
β”‚ https://why-agent...         β”‚
β”‚                              β”‚
β”‚  UI + agent + tools + data   β”‚
β”‚  all in one Python process   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚ HTTPS, OpenAI-compat
               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        $1.99/hr
β”‚ AMD MI300X droplet           β”‚        (when ON)
β”‚ vLLM + Llama-3.3-70B BF16    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Architecture diagram

Three logical pieces:

  1. The model β€” vLLM serving Llama 70B on MI300X. Heavy, expensive.
  2. The agent β€” Python code (LangGraph). Light, runs anywhere.
  3. The data β€” DuckDB on Parquet + a YAML semantic layer. Tiny.

Everything except the model lives inside one Streamlit app on Streamlit Cloud. The model is reached via HTTPS to the AMD droplet.

Why this split

  • The model needs a GPU. Nothing else does.
  • Streamlit Cloud is free; GPU droplets are $1.99/hr.
  • The agent can use Anthropic API as a fallback when the GPU is off.
  • Iteration speed: code changes don't redeploy the GPU.

Three model backends, env-switchable

MODEL_BACKEND=minimax β†’ MiniMax API (MiniMax-M2.7). Use Days 1-2 (no GPU). MODEL_BACKEND=vllm β†’ MI300X. Use Day 3+ for integration & demo. MODEL_BACKEND=replay β†’ Pre-recorded traces. For demo when GPU off.

This is critical infrastructure β€” implement on Day 1.


5. The agent

Loop

plan β†’ decompose β†’ drill β†’ cross-check β†’ critique β†’ report
↑                                          β”‚
└────── if evidence weak, loop back β”€β”€β”€β”€β”€β”€β”€β”˜

A LangGraph state machine. Each step is an explicit node. State persists across the whole investigation.

When critique returns VERDICT: weak, its justification text is stored in state.critique_feedback and injected into the next phase's system prompt as a targeted directive β€” so the agent retries the specific gap identified, rather than re-exploring from scratch and burning retry budget.

The four tools

We deliberately have only four. Fewer integrations = fewer demo failure modes. Hypothesis tracking lives in agent prompt + memory, not in a tool.

Tool Purpose
inspect_schema(table) Returns columns, types, sample values, business descriptions from semantic layer
run_sql(query) Executes a read-only DuckDB query, returns rows
decompose_metric(metric, dims) Slices metric by each dim, ranks slices by anomaly magnitude
compare_periods(metric, before, after, segment) Quantifies how a slice changed between two windows

Tools were cut from six. Resist re-adding them. The agent gets smarter by reasoning better, not by having more tools.

What "beyond simple RAG" means here

Simple RAG why-agent
1 retrieval Many SQL queries, planned
1 generation Multi-step loop
Static Hypothesis-driven branching
No self-eval Self-critique before reporting
Document-bound Operates on live structured data

6. Data + semantic layer

Dataset

why-agent works against any user-provided data β€” drop Parquet files into data/parquet/ and point the semantic layer at them. There is no fixed dataset baked into the project.

The current demo dataset is a marketing/CRM extract:

File Contents
campaigns.parquet Campaign metadata and performance metrics
client_first_purchase_date.parquet Customer acquisition timeline
holidays.parquet Holiday calendar for seasonality context
messages.parquet Message-level send/open/click events

DuckDB view registration: All *.parquet files in PARQUET_DIR are registered as independent DuckDB views named after their file stem. There are no implicit JOINs between views β€” cross-table queries require explicit INNER JOIN / LEFT JOIN clauses. The agent learns which tables need joining from the joins section returned by inspect_schema, and writes SQL with explicit joins. This keeps the system generic: any set of parquet files is registered as-is, and the semantic layer YAML defines the relationships.

The semantic layer

A single data/semantic_layer.yml file. Defines:

  • Tables: column names, types, business meaning, and primary keys (supports composite PKs via a list)
  • Metrics: named measures the agent can compute (open_rate, messages_sent, etc.)
  • Dimensions: slicing axes for decomposition (channel, topic, eventually_converted, etc.)
  • Relationships: join keys between tables
  • Filters: globally applied rules (e.g., exclude test campaigns)
  • Value labels: what enum values mean (e.g., bulk vs trigger vs transactional)
  • Gotchas: dataset-specific analysis pitfalls surfaced to the agent at plan time

This artifact is the contract between Isa and Mapo. Isa produces it; Mapo consumes it. Once it stabilizes, both of us build forward without blocking each other.


7. Tech stack (locked)

Layer Choice
Hosting Streamlit Community Cloud (free, public URL)
Frontend Streamlit
Backend LangGraph agent + tools (pure Python, in-process)
Orchestration LangGraph
Model Llama-3.3-70B-Instruct, BF16
Inference vLLM on ROCm
Hardware AMD MI300X (1 GPU)
Data engine DuckDB
Data format Parquet
Semantic layer YAML
Tracing LangSmith (free tier)
Validation Pydantic
Deps uv
Lint Ruff
Containers Docker

8. Demo strategy

Two modes the public URL supports

  • 🟒 Live MI300X mode β€” full live agent against the GPU. Available during scheduled windows we post.
  • πŸ“Ό Replay mode β€” pre-recorded canonical investigations, played back from saved JSON traces. Always available.
  • πŸ’¬ Anthropic fallback β€” for ad-hoc judge questions when GPU is off.

Demo scenarios (rehearsed, deterministic)

  1. Why did message open rate drop in the most recent campaign? Expected: specific campaign/segment underperformance, tied to send-time or audience slice.
  2. Why did new customer acquisition spike in a particular month? Expected: campaign concentration or holiday effect in the acquisition data.
  3. Why is weekend engagement consistently lower than weekday? Expected: structural pattern, agent should distinguish from anomaly.

Scenario 1 is our hero demo.

One thing to verify early

Run scenario 1 end-to-end on Day 3. If the agent can't reach the conclusion from the data alone, we need a different question. Don't wait until Day 5 to find out.


9. Cost & GPU budget

We have $100 in AMD Developer Cloud credits = ~50 GPU-hours.

Phase GPU hrs $
Day 0 β€” validate vLLM + Llama 70B serves 3 $6
Days 1–2 β€” local build (no GPU) 0 $0
Day 3 β€” first integration 4 $8
Day 4 β€” iteration & prompt tuning 6 $12
Day 5 β€” record replays + polish 8 $16
Day 6 β€” demo day 12 $24
Reserve 17 $34
Total 50 $100

Rules

  • DESTROY the droplet, don't stop it. Stopped droplets still bill.
  • Credits expire 30 days after activation. Activate close to use.
  • One GPU only β€” never the 8x ($15.92/hr will burn through credits in 6 hours).
  • ~$5 of our own money for a 200GB block volume to cache model weights across droplet recreations is a sensible add-on. Worth confirming.

10. Team split & ownership

The split mirrors the architecture: Isa owns data + meaning, Mapo owns agent + interface. The handoff is semantic_layer.yml.

Isa owns

  • Demo dataset β€” Parquet files in data/parquet/
  • Semantic layer YAML
  • Ground-truth validation: "is the agent's answer actually right?"
  • Build-in-public post about the data layer

Mapo owns

  • LangGraph state machine + agent loop
  • The four tools (Pydantic schemas, implementations)
  • LLM client (multi-backend switching)
  • Streamlit UI (chat, reasoning trace, replay picker)
  • vLLM-on-MI300X setup scripts
  • Streamlit Cloud deployment
  • Build-in-public post about the agent design

Shared

  • Demo script & live narration
  • README, submission video, final pitch
  • Day-end sync (15 min) to surface blockers

11. Day-by-day plan (rough)

Day Mapo Isa Joint
0 (pre) Validate vLLM on MI300X, write start_vllm.sh, destroy droplet Prepare demo Parquet files, drop into data/parquet/ Repo scaffolded, README locked
1 Agent loop + tools against Anthropic API Draft semantic_layer.yml v1 Agree on tool I/O signatures
2 First end-to-end agent investigation working Refine semantic layer, prep demo scenarios Smoke-test with provided dataset
3 Switch to vLLM backend, run scenario 1 live Validate scenario 1 ground truth First real demo run; identify gaps
4 Prompt tuning, self-critique node Scenario 2 + 3 ground truth Build-in-public posts
5 Streamlit polish, record replays, deploy to Cloud Final dataset cleanup, write evaluation notes Rehearse demo
6 Final fixes, submission prep Final fixes, submission prep Submit + pitch

What's allowed to slip

  • Scenario 3 (the structural-pattern one)
  • Streamlit polish beyond functional
  • Build-in-public posts after the first two

What is NOT allowed to slip

  • Scenario 1 working end-to-end by Day 3
  • Public URL up and demoable by Day 5
  • Replay mode working by Day 5

12. Risks & open questions

Risks we can name today

  1. vLLM-on-MI300X setup stalls Day 1. Mitigation: validate on Day 0, keep Anthropic API fallback always wired.
  2. The "right answer" for our chosen scenario isn't reachable from the data alone. Mitigation: pick scenario on Day 0; smoke-test end-to-end by Day 3.
  3. Streamlit Cloud RAM limit exceeded by data + agent + LangGraph process overhead. Mitigation: keep Parquet files under 300 MB total; profile on Day 2.
  4. Live demo timing variance β€” agent takes 90s instead of 60s. Mitigation: don't claim "60 seconds"; frame as "minutes vs hours."
  5. One of us gets sick. Mitigation: README + repo are clear enough the other can demo solo.

Open questions to resolve early

  • Which specific question is our hero demo?
  • Is the semantic layer accurate enough for the demo dataset?
  • Do we pay $5 for the model-weights block volume?
  • Do we want a custom domain, or is *.streamlit.app fine? (default: streamlit.app fine)

13. Working agreements

  • Decisions go in this doc. If we changed our minds, edit it. No re-litigating in chat.
  • Day-end sync, 15 min. Just blockers and tomorrow's priority.
  • Push to main freely until Day 4. From Day 5: PRs only.
  • No new tools, libraries, or scope past Day 3 without explicit agreement from both of us.
  • The semantic layer is a contract. Once stable, breaking changes require a heads-up.

14. Implementation status

Component Status
LLM client β€” 3 backends (minimax, vllm, replay) βœ… done
Pydantic state model (InvestigationState) βœ… done
LangGraph state machine (6-phase loop) βœ… done
inspect_schema tool βœ… done β€” derived dimensions surface SQL expression
run_sql tool βœ… done
compare_periods tool βœ… done
decompose_metric tool βœ… done
System + critique prompts βœ… done
REPL for local testing βœ… done
Streamlit UI βœ… done
Demo dataset in data/parquet/ βœ… done
Replay recording script ⬜ pending
vLLM Docker + MI300X scripts ⬜ pending

15. Getting started (dev setup)

# Prerequisites: Python 3.12+, uv installed
git clone https://github.com/Isa-Mapo-Hackathon/why-agent
cd why-agent
uv sync

# Copy and fill in .env
cp .env.example .env
# Set MINIMAX_API_KEY (get from MiniMax dashboard)
# PARQUET_DIR defaults to data/parquet β€” point at data/dev for the toy dataset

# Run the test suite (120 tests, no network required)
uv run pytest

# Interactive REPL against the real MiniMax API
uv run python scripts/repl_graph.py
# > Q: Why did PR activity drop on Oct 21 2018?

# Lint + format (must be clean before any commit)
uv run ruff check --fix && uv run ruff format

# Run the app β€” FastAPI backend + Next.js frontend (default)
uv run uvicorn client.backend.main:app --reload --port 8000  # Terminal 1
cd client/frontend && npm run dev                             # Terminal 2

# Alternative: Streamlit (single terminal)
uv run streamlit run streamlit_app.py

Environment variables

Variable Required Description
MODEL_BACKEND Yes minimax / vllm / replay
MINIMAX_API_KEY When MODEL_BACKEND=minimax MiniMax API key
VLLM_ENDPOINT When MODEL_BACKEND=vllm e.g. http://host:8000/v1
REPLAY_SCENARIO_ID When MODEL_BACKEND=replay Scenario JSON filename (without .json)
PARQUET_DIR No Path to Parquet files (default: data/parquet)
SEMANTIC_LAYER_PATH No Default: data/semantic_layer.yml

16. Running Locally (Full Stack)

Default: FastAPI + Next.js

Terminal 1 β€” FastAPI backend:

uv run uvicorn client.backend.main:app --reload --port 8000

Backend runs at http://localhost:8000. Check health at http://localhost:8000/api/health.

Terminal 2 β€” Next.js frontend:

cd client/frontend
npm install  # first time only
npm run dev

Frontend runs at http://localhost:3000.

Alternative: Streamlit UI

Single terminal, no frontend setup needed. Use this for quick iteration on the agent loop.

uv run streamlit run streamlit_app.py

Opens at http://localhost:8501.

Common development commands

Task Command
Install/sync deps uv sync
Add dependency uv add <package> (runtime) or uv add --dev <package> (dev)
Run all tests uv run pytest -v
Run one test file uv run pytest tests/test_agent_smoke.py -v
Lint & auto-fix uv run ruff check --fix
Format code uv run ruff format
Type check (optional) uv run pyright
Run FastAPI backend uv run uvicorn client.backend.main:app --reload --port 8000
Run Next.js frontend cd client/frontend && npm run dev
Run Streamlit (alt) uv run streamlit run streamlit_app.py
Build Next.js cd client/frontend && npm run build
Build Docker image docker build -t why-agent:latest .

17. Development & Testing

Running tests

# All tests
uv run pytest

# Single file
uv run pytest tests/test_tools.py -v

# Single test
uv run pytest tests/test_tools.py::test_inspect_schema -v

# With print output
uv run pytest -s

Tests are smoke tests β€” we verify that tools run without crashing and return the expected JSON shape. Mocking is minimal.

Code quality gates (required before commit)

uv run ruff check --fix    # Fix lint issues
uv run ruff format         # Format code

Both must pass before committing. Set up a pre-commit hook to automate:

cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
uv run ruff check --fix && uv run ruff format || exit 1
EOF
chmod +x .git/hooks/pre-commit

Using the REPL (interactive testing)

# Against MiniMax API (requires MINIMAX_API_KEY)
export MODEL_BACKEND=minimax
uv run python scripts/repl_graph.py
# > Q: Why did message open rate drop?
# > Q: Why does weekend engagement differ?

# Against replay (no API key needed)
export MODEL_BACKEND=replay
export REPLAY_SCENARIO_ID=scenario_1
uv run python scripts/repl_graph.py

18. Deployment to HF Spaces

why-agent is designed to deploy to Hugging Face Spaces via Docker. The included Dockerfile is multi-stage and includes everything: Python agent, FastAPI backend, Next.js frontend, and nginx reverse proxy.

Quick deploy (3 steps)

1. Push code to HF Spaces:

# Set up remote once (replace with your Spaces URL)
git remote add space https://huggingface.co/spaces/YOUR_USERNAME/why-agent.git

# Then push (HF Spaces auto-detects Dockerfile and builds)
git push space main

2. Set environment variables in HF Spaces Settings:

Go to Space Settings > Variables and add:

Variable Value
MODEL_BACKEND replay (recommended) or vllm if you have a GPU endpoint
MINIMAX_API_KEY Only if using MODEL_BACKEND=minimax
VLLM_ENDPOINT Only if using MODEL_BACKEND=vllm (e.g. http://vllm-api.example.com:8000/v1)
HF_DATASET_ID Optional: e.g. username/why-agent-data (auto-downloads at boot)

3. Verify:

curl https://YOUR_SPACE_URL/api/health

Should return: {"ok": true}

Model backends explained

Backend Use case Cost Setup
replay Demo when GPU offline, pre-recorded scenarios Free Set REPLAY_SCENARIO_ID=scenario_1
minimax Fallback LLM for ad-hoc questions ~$0.01/query Set MINIMAX_API_KEY
vllm High-quality, fast inference on GPU $1.99/hr (AMD MI300X) Set VLLM_ENDPOINT

Recording demo scenarios for offline playback

When a scenario works end-to-end, record it:

export MODEL_BACKEND=minimax
export MINIMAX_API_KEY=your-key
uv run python scripts/record_replay.py --scenario scenario_1

This saves replays/scenario_1.json. Commit it and deploy with MODEL_BACKEND=replay.

Docker: build and run locally

# Build
docker build -t why-agent:latest .

# Run
docker run -p 7860:7860 -e MODEL_BACKEND=replay why-agent:latest

Then visit http://localhost:7860.

Environment variables reference (complete)

Variable Default Description
MODEL_BACKEND β€” LLM backend: minimax, vllm, or replay
MINIMAX_API_KEY β€” MiniMax API key (if using minimax backend)
VLLM_ENDPOINT β€” vLLM server URL (if using vllm backend; include /v1)
REPLAY_SCENARIO_ID β€” Scenario ID for replay mode (filename without .json)
PARQUET_DIR /app/data/parquet Path to Parquet dataset directory
SEMANTIC_LAYER_PATH /app/data/semantic_layer.yml Path to semantic layer YAML
HF_DATASET_ID β€” HF Dataset ID to auto-download at boot (optional)
LANGSMITH_API_KEY β€” LangSmith API key for tracing (optional)

Health check endpoints

# Health check
curl http://localhost:7860/api/health
# Returns: {"ok": true}

# Demo questions
curl http://localhost:7860/api/demo-questions
# Returns: {"questions": [...]}

# Investigate (POST)
curl -X POST http://localhost:7860/api/investigate \
  -H "Content-Type: application/json" \
  -d '{"question": "Why did open rate drop?"}'
# Streams Server-Sent Events (SSE)

Troubleshooting deployment

Issue Solution
Build fails with npm error Ensure Node 20+ installed; run npm install --legacy-peer-deps locally
API returns 500 Check HF Spaces logs; verify PARQUET_DIR and SEMANTIC_LAYER_PATH exist
vLLM endpoint unreachable Verify VLLM_ENDPOINT includes /v1; check GPU server is running
Data not loading Set HF_DATASET_ID to auto-download, or manually COPY Parquet files into Dockerfile
Replay scenario not found Verify REPLAY_SCENARIO_ID matches filename in replays/ (without .json)

19. Architecture & Project Structure

why-agent/
β”œβ”€β”€ agent/                           # Core agent logic
β”‚   β”œβ”€β”€ graph.py                     # LangGraph state machine (6-phase loop)
β”‚   β”œβ”€β”€ state.py                     # Pydantic InvestigationState model
β”‚   β”œβ”€β”€ client.py                    # Multi-backend LLM client
β”‚   β”œβ”€β”€ constants.py                 # Named constants (backends, tools, demo questions)
β”‚   β”œβ”€β”€ tools/                       # The four tools
β”‚   β”‚   β”œβ”€β”€ inspect_schema.py       # Returns table metadata + business context
β”‚   β”‚   β”œβ”€β”€ run_sql.py              # Execute read-only DuckDB queries
β”‚   β”‚   β”œβ”€β”€ compare_periods.py      # Quantify metric change between windows
β”‚   β”‚   └── decompose_metric.py     # Slice metric by dimensions, rank anomalies
β”‚   └── prompts/                     # System + critique prompts (markdown)
β”‚       β”œβ”€β”€ system.md
β”‚       └── critique.md
β”‚
β”œβ”€β”€ client/
β”‚   β”œβ”€β”€ backend/                     # FastAPI server
β”‚   β”‚   β”œβ”€β”€ main.py                 # GET /health, POST /api/investigate
β”‚   β”‚   β”œβ”€β”€ deps.py                 # Dependency injection
β”‚   β”‚   β”œβ”€β”€ sse.py                  # Server-Sent Events formatting
β”‚   β”‚   └── tests/
β”‚   └── frontend/                    # Next.js app (React + TypeScript)
β”‚       β”œβ”€β”€ src/app/page.tsx        # Main UI page
β”‚       β”œβ”€β”€ src/app/api/investigate # Next.js API route (optional)
β”‚       └── package.json
β”‚
β”œβ”€β”€ data/
β”‚   β”œβ”€β”€ parquet/                     # Dataset files (user-provided; gitignored)
β”‚   β”œβ”€β”€ semantic_layer.yml           # Business metadata, metrics, dimensions, joins
β”‚   └── root_cause/                  # Ground-truth documentation
β”‚
β”œβ”€β”€ replays/                         # Pre-recorded investigation traces (JSON)
β”‚   └── scenario_1.json
β”‚
β”œβ”€β”€ tests/                           # Python smoke tests
β”‚   β”œβ”€β”€ test_tools.py               # Tool execution and output shape
β”‚   β”œβ”€β”€ test_client_backends.py     # Verify 3 backends (minimax, vllm, replay)
β”‚   └── test_agent_smoke.py         # End-to-end agent smoke test
β”‚
β”œβ”€β”€ docker/                          # Container config
β”‚   β”œβ”€β”€ Dockerfile                  # Multi-stage: Next.js + Python + nginx
β”‚   β”œβ”€β”€ entrypoint.sh               # Boot script (handles HF Dataset download)
β”‚   β”œβ”€β”€ nginx.conf                  # Reverse proxy (routes / to Next.js, /api/* to FastAPI)
β”‚   └── supervisord.conf            # Process management (nginx, FastAPI, Next.js)
β”‚
β”œβ”€β”€ scripts/                         # Utilities
β”‚   β”œβ”€β”€ repl_graph.py               # Interactive REPL for testing the agent
β”‚   └── record_replay.py            # Save a scenario as replay JSON
β”‚
β”œβ”€β”€ streamlit_app.py                # Streamlit UI (standalone, no backend needed)
β”œβ”€β”€ pyproject.toml                  # Dependencies + test config
β”œβ”€β”€ Dockerfile                      # Deployment image
β”œβ”€β”€ .env.example                    # Environment template
β”œβ”€β”€ CLAUDE.md                       # Implementation decisions & constraints
β”œβ”€β”€ README.md                       # This file (project overview + business context)
└── docs/
    └── why-agent-architecture.png  # Diagram

20. Coding conventions

Per CLAUDE.md, follow these when writing code:

  1. Sync by default β€” DuckDB and Streamlit are sync. Use async def only at the LLM boundary.
  2. Pydantic v2 β€” All structured data (tool inputs/outputs, state, semantic layer).
  3. Type annotations β€” Required on public functions (args + return type).
  4. No print() β€” Use logger = logging.getLogger(__name__) in agent code.
  5. No magic strings β€” Backend names, tool names, scenario IDs go in agent/constants.py.
  6. Tool docstrings for the LLM β€” Write them as if the model will read them (be descriptive about what it does and when to use it).

Example tool implementation

from pydantic import BaseModel, Field
import logging

logger = logging.getLogger(__name__)

class MyToolInput(BaseModel):
    query: str = Field(description="A human-readable query or metric name.")

class MyToolOutput(BaseModel):
    result: dict
    error: str | None = None

def my_tool(args: MyToolInput) -> dict:
    """Use this tool to analyze X. Returns a dict with 'result' (the data) and optional 'error'."""
    try:
        result = ...
        return {"result": result}
    except Exception as exc:
        logger.exception("Tool failed for query: %s", args.query)
        return {"error": str(exc), "hint": "Try phrasing the query differently"}

21. Locked decisions (do not change without explicit approval)

These decisions are locked per CLAUDE.md. Changing any requires discussion:

Decision Value Why
Architecture Single agent (not multi-agent) Simpler to debug, easier to understand agentic fundamentals
Tool count 4 tools (fixed) Fewer integrations = fewer demo failure modes
Orchestration LangGraph (not CrewAI, AutoGen, etc.) Explicit state machine, good tracing, community support
Model (prod) Llama-3.3-70B (vLLM) Open-source, fast on MI300X, no licensing
Model (dev) MiniMax-M2.7 (API fallback) No GPU required, quick iteration
Data engine DuckDB on Parquet Embedded, column-oriented, single query engine
Semantic layer Single YAML file (hand-written) Simple, no tooling overhead, easy to version
UI Streamlit (primary) + Next.js (secondary) Free hosting, rapid iteration, good for demos
Hosting HF Spaces (primary) + Streamlit Cloud (backup) Free, simple, community-friendly
License MIT Open-source, permissive

If a task seems to require changing one of these, pause and ask before proceeding.


22. Risks & known limitations

  1. Parquet size β€” Keep total Parquet data under 500 MB to fit in HF Spaces' memory limit. Profile on Day 2.
  2. Investigation latency β€” Agent might take 60–120 seconds on a fallback model. Frame demos as "minutes vs hours," not "60 seconds."
  3. GPU availability β€” The MI300X droplet costs $1.99/hr. Use MODEL_BACKEND=replay when the GPU is off.
  4. Concurrent requests β€” HF Spaces free tier queues additional requests (no parallelism). For production, use a dedicated server.
  5. Replay maintenance β€” Scenarios must be re-recorded if the agent loop changes significantly.

23. Resources & links