- Python 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| sample_data | ||
| skills/product-import | ||
| src/distru_agent | ||
| .env.example | ||
| .gitignore | ||
| .python-version | ||
| chat_app.py | ||
| langgraph.json | ||
| notes.md | ||
| pyproject.toml | ||
| README.md | ||
| run_stub.py | ||
| uv.lock | ||
Distru Agentic Harness — Prototype
A chat-testable prototype of the agentic harness described in ../Tech Spec.md,
built on Deep Agents (top layer) on LangGraph. The first vertical slice
is product CSV/XLSX import.
It demonstrates the whole harness shape from the spec:
- Core agent with a durable Distru domain system prompt (
src/distru_agent/agent.py) - Skill layer (
skills/product-import/) loaded on demand - Subagent delegation via the
tasktool (generic subagent, auto-provisioned by deep-agents) - Context tool:
customer_attributes - Action tools:
map_products_file(dry-run, never uploads) andupload_products(the only real, gated action) - Human-in-the-loop:
upload_productsrequires explicit consent - State: the LangGraph platform checkpointer provides conversation + interrupt persistence automatically (no custom one set)
Because there is no real Distru account, a small stub Distru server
(src/distru_agent/server/stub_distru.py) faithfully emulates the internal contract:
GET /customer/{id}/attributes and POST /upload-products (chunking, validation,
partial upload, row-mapped error CSV). The agent's tools call it over HTTP.
Requirements
uv(Python env manager). It provisions a managed Python 3.12 — system Python being older is fine.
Setup
cd distru-prototype
uv sync # create .venv and install deps
cp .env.example .env # then edit .env and paste your OpenAI key
In .env set:
OPENAI_API_KEY=sk-...
# optional
# OPENAI_MODEL=gpt-5.4-nano
Dependencies are pinned in pyproject.toml. The stub server needs
python-multipart (already included) for uploads.
Run (two processes)
Because the tools call the stub over HTTP, start the stub first, then the chat app.
1) Start the stub Distru server
uv run python run_stub.py # serves http://127.0.0.1:8000
Optional: DISTRU_STUB_URL and DISTRU_CUSTOMER_ID env vars override the URL and
target customer (defaults http://127.0.0.1:8000 and 1001).
2) Start the chat app
uv run python chat_app.py # serves http://127.0.0.1:8464
Open http://127.0.0.1:8464 in your browser. This is a minimal FastAPI chat page that accepts CSV/XLSX uploads (unlike LangGraph Studio's file picker, which only accepts images and PDFs). It runs the same agent graph locally with a thread + HITL consent gate.
Optional: DISTRU_CHAT_HOST and DISTRU_CHAT_PORT env vars override the bind
address (default 127.0.0.1:8464).
How to test the import
In the chat page, attach a CSV/XLSX or reference a sample file path and send:
I have a product catalog file at ./sample_data/products_messy.csv
Please import it as new products.
The flow: the agent delegates to a subagent via the task tool. The subagent
loads the customer's attributes, maps the file (dry-run), surfaces new vs existing
entities, and — because upload_products is interrupt gated — pauses for your
approval before any real upload. Inspect the map report (8 products, 1
duplicate auto-dropped, new strains + a new Pre-Rolls category, 2 fixable
issues: a Metrc-disallowed : in one name and an unresolvable oz unit) and
approve/reject.
The sample file deliberately includes edge cases from Tech Spec.md §4: exact
duplicate, Metrc-disallowed name, a new category, new strains, an unresolvable unit,
differing vendors.
Uploading a file in chat
Click Attach file in the chat page, select a CSV/XLSX, and send a message.
The file is saved to uploads/ and the agent proceeds with the normal import flow.
Notes on the harness mapping (spec -> framework)
| Tech Spec | This prototype |
|---|---|
| Core agent + lean system prompt | create_deep_agent(system_prompt=...) |
| Skill layer | skills=["./skills/"] (SKILL.md, on-demand load) |
| Subagent delegation | Generic subagent via task tool (auto-provisioned) |
customer_attributes (context) |
tools/customer_attributes.py |
map_products_file (dry-run) |
tools/map_products_file.py |
upload_products (gated action) |
tools/upload_products.py |
| User consent / ask-the-user | Deep Agents interrupt_on={"upload_products": ...} (HITL) |
| State (conversation / exec / artifacts) | LangGraph platform checkpointer + temp files by handle |
Extending
- Add a capability = add a tool + (optionally) a skill, registered in
agent.py. - The subagent + skill could later be invoked headless by a workflow trigger (cron / sheet scan) in a real LangGraph deployment.
Layout
distru-prototype/
├── langgraph.json # Studio/dev entry (graph = src/distru_agent/agent.py:graph)
├── pyproject.toml / .python-version
├── .env.example
├── run_stub.py # start the stub Distru server on :8000
├── chat_app.py # chat UI (FastAPI, port 8464) — primary test surface
├── sample_data/
│ └── products_messy.csv # deliberately messy catalog to import
├── skills/
│ └── product-import/SKILL.md # domain knowledge + consent policy
└── src/distru_agent/
├── agent.py # create_deep_agent -> graph
├── config.py # stub URL + customer id
├── distru_models.py # Pydantic domain models
├── middleware.py # saves chat-uploaded files, injects path
├── tools/
│ ├── customer_attributes.py
│ ├── map_products_file.py
│ └── upload_products.py
└── server/
└── stub_distru.py # FastAPI stub backend