lang_ai_agent
A LangGraph agent backend built around production patterns: a streaming HTTP API, durable state that survives restarts, a human-approval gate for side-effecting tools, deterministic tests that never call a real LLM, and built-in observability.
It ships as an Ops Copilot demo (multi-store retail: "what's about to stock out?" → "send the reorder email"), but the runtime is domain-agnostic — the tools are the only retail-specific part. Swap them (or plug in your own MCP servers) and keep everything else.
Status — v0.1.0 is released on PyPI and gated by
make check(ruff, pyright strict, pytest; 100% coverage on the graph core). Releases go through GitHub Actions Trusted Publishing with a maintainer approval step. Design docs live underdocs/.
How it works
The agent calls read-only tools freely. Any side-effecting tool stops the graph at a LangGraph interrupt() and waits for a human to approve or reject it over the API. The graph is checkpointed at that point, so the server can restart in between. A rejection with a comment goes back to the model, which revises its draft and asks again.
flowchart LR
C[Client / curl] -- "HTTP + SSE (Bearer)" --> A[FastAPI · api/app.py]
A --> agent
agent -- tool_calls --> route
route -- safe --> safe_tools --> agent
route -- effect --> approval
approval -. "interrupt() ⏸ human approves" .-> effect_tools --> agent
agent -- no tool_calls --> END
The only edge into effect_tools passes through approval. That is not a convention — a test walks the compiled graph and fails if any other path appears.
Install
Requires Python 3.12+.
pip install lang-ai-agent # or: uv add lang-ai-agent
uv tool install lang-ai-agent # or, as a standalone CLI on your PATH
To work from a checkout instead, install uv and run uv sync; every command below then takes a uv run prefix.
Quickstart
lang-ai-agent init # pick a provider, paste your API key → writes .env (mode 0600)
lang-ai-agent serve # http://127.0.0.1:8000 — fails fast if the key is missing
init supports Anthropic (default), OpenAI, xAI and Google; MODEL uses LangChain's provider:model form, so any other provider init_chat_model supports works as well. Your key is written only to the git-ignored .env.
Talk to it from another terminal (TOKEN is the APP_BEARER_TOKEN that init printed):
H=(-H "Authorization: Bearer $TOKEN" -H 'content-type: application/json')
TID=$(curl -s -X POST "${H[@]}" localhost:8000/threads | jq -r .thread_id)
curl -sN -X POST "${H[@]}" localhost:8000/threads/$TID/messages \
-d '{"content":"Which items at store main will stock out next week? Summarize as a table."}'
The response is a Server-Sent Events stream: tool_start/tool_end for check_stockout, token events carrying the table, then usage and done.
API
| Method · path | Body | What it does |
|---|---|---|
POST /threads |
— | Issue a thread_id |
POST /threads/{id}/messages |
{content} |
Run the graph; SSE stream |
GET /threads/{id}/state |
— | last_message, pending action, usage, awaiting_approval |
POST /threads/{id}/approve |
{approved, comment?} |
Resume from the interrupt; SSE stream |
DELETE /threads/{id} |
— | Delete the thread's history (every checkpoint) |
SSE events (Pydantic-typed, discriminated on type): token · tool_start · tool_end · interrupt (pending action + draft) · usage · done · error. A stream ends with either one interrupt or usage → done.
60-second demo
lang-ai-agent serve— one JSON log line, server up.- Ask what will stock out at store
main→ tool call, streamed table. - "Send the reorder email for the at-risk items" → suggestions are fetched, a draft is written, and the stream stops at
interruptshowing the recipient and draft. GET /state→awaiting_approval: true.POST /approve {"approved": true}→ the email tool runs (dry-run by default), the agent reports back,usage→done.
The full script with timings and a restart-resilience variant is in docs/DEMO.md.
Real-model smoke
lang-ai-agent init # once — your key goes to .env only
lang-ai-agent smoke # scenario 1 (query) + scenario 2 (draft → y/n approval in the console)
lang-ai-agent smoke --mcp # same, with the real MCP servers from mcp_servers.json
From a checkout, make smoke runs the same thing.
The smoke always runs dry-run, whatever .env says, and costs three to four model calls (cents on a Sonnet-class model). Everything else runs without a key: make check makes zero network calls.
Why it is built this way
- Approval as topology, not a flag. Side-effecting tools are reachable only through the
approvalnode, and a graph-structure test enforces it. Sending is double-gated: the interrupt andSEND_MODE=live. - One run per thread at a time.
/messages,/approveandDELETEon the same thread are serialized in-process, so a duplicated approval can never run the effect twice, and a message sent while the thread waits for approval gets a 409 instead of forking its history. - The model is a script in tests.
ScriptedChatModelreplays a fixed sequence ofAIMessages (tool calls included) and fails loudly if the script is exhausted or diverges. With the model scripted, the graph is a state machine and every path — approve, reject-and-revise, tool failure, restart mid-interrupt — is a deterministic test. Real models appear only in the smoke. - State stays small. State is serialized at every checkpoint, so it holds messages and minimal metadata; large tool results are summarized before they enter it.
- Static types as the cheapest feedback loop. pyright strict + Pydantic v2 at every boundary (requests, model output, MCP responses), no
Anyreturns, and every# type: ignorecarries a reason. - Fail at startup, not on the first request. A missing provider key or bearer token is a
ConfigErrorwith the fix in the message, raised before the server binds. - Provider-agnostic, MCP-native.
init_chat_modelfor the model;langchain-mcp-adaptersto mount MCP servers as tools, each mapped to safe/effect (unlisted tools default to effect).
Development
uv sync # dev dependencies included
make check # ruff check + pyright strict + pytest (core coverage gate ≥ 90%)
make dev # uvicorn with --reload
src/lang_ai_agent/
core/ state.py (AgentState, PendingAction, Usage) · graph.py (StateGraph) · tools_spec.py (safe/effect)
adapters/ llm.py (providers) · checkpoint.py (AsyncSqliteSaver) · mcp_loader.py · effects.py · observability.py
api/ app.py (FastAPI assembly) · sse.py (event schema + mapper) · auth.py
cli.py init · serve · smoke smoke.py real-model smoke logic
tests/ helpers (ScriptedChatModel, MockEffects, FixedClock) · unit · component · e2e (API-level scenarios)
CI runs make check on every push and pull request (.github/workflows/ci.yml). A v* tag runs publish.yml: build → TestPyPI → PyPI, the last step behind a required maintainer approval (docs/RELEASE.md). That approval is the maintainer's; installing and running the package never asks anyone for approval.
Docs
| Doc | Contents |
|---|---|
CLAUDE.md |
Agent steering: stack, commands, conventions, guardrails |
docs/SPEC.md |
Product spec: goals, non-goals, scenarios, roadmap |
docs/DESIGN.md |
Technical design: graph, state, API, tool classes, MCP, env, onboarding, packaging |
docs/TESTING.md |
Test strategy: scripted model, golden trajectories, edge-case checklist |
docs/TASKS.md |
Task backlog with machine-checkable completion criteria |
docs/WORKFLOW.md |
AI-native development rules for this repo |
docs/DEMO.md |
The 60-second demo script |
docs/RELEASE.md |
PyPI release runbook: Trusted Publishing setup, tag rules, procedure |
This repo is developed doc-first: spec and design are updated before code, implementation is done task-by-task by Claude Code, and make check is the shared gate.
Roadmap
- v0.1 — single-agent graph + approval gate + FastAPI SSE + deterministic tests + onboarding CLI + CI + PyPI release
- v0.2 — PostgresSaver, supervisor multi-agent, always-on MCP server connections
- v0.3 — evaluation harness (golden-trajectory regression + eval sets), cost reports, Docker template
License
MIT © 2026 Trapa-Eureka.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lang_ai_agent-0.1.1.tar.gz.
File metadata
- Download URL: lang_ai_agent-0.1.1.tar.gz
- Upload date:
- Size: 37.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9792a38f1a2af652126aba691cd5a890a7aeba2445652f3602775ab57dad8a93
|
|
| MD5 |
24017d21a7e81c93bd72dbceba0b32c4
|
|
| BLAKE2b-256 |
42665313d96657ffb8137591cf5905790af187150af628ba3b7676c0d9d1e1fc
|
Provenance
The following attestation bundles were made for lang_ai_agent-0.1.1.tar.gz:
Publisher:
publish.yml on Trapa-Eureka/lang-ai-agent
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lang_ai_agent-0.1.1.tar.gz -
Subject digest:
9792a38f1a2af652126aba691cd5a890a7aeba2445652f3602775ab57dad8a93 - Sigstore transparency entry: 2724197075
- Sigstore integration time:
-
Permalink:
Trapa-Eureka/lang-ai-agent@f7f604efda94214985b4510692c16d4454b4aaba -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/Trapa-Eureka
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f7f604efda94214985b4510692c16d4454b4aaba -
Trigger Event:
push
-
Statement type:
File details
Details for the file lang_ai_agent-0.1.1-py3-none-any.whl.
File metadata
- Download URL: lang_ai_agent-0.1.1-py3-none-any.whl
- Upload date:
- Size: 47.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c832254b458364e0eefd1741623a98684a742c9fc1f4e310f14fede121de3a3f
|
|
| MD5 |
98fe5d2b63680ebcfc273d973f5dc4ab
|
|
| BLAKE2b-256 |
d2a4cb5694a92957b8aeef3ed47ad7282d35e1368a42846f1118aeadd0b5347b
|
Provenance
The following attestation bundles were made for lang_ai_agent-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on Trapa-Eureka/lang-ai-agent
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lang_ai_agent-0.1.1-py3-none-any.whl -
Subject digest:
c832254b458364e0eefd1741623a98684a742c9fc1f4e310f14fede121de3a3f - Sigstore transparency entry: 2724197720
- Sigstore integration time:
-
Permalink:
Trapa-Eureka/lang-ai-agent@f7f604efda94214985b4510692c16d4454b4aaba -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/Trapa-Eureka
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f7f604efda94214985b4510692c16d4454b4aaba -
Trigger Event:
push
-
Statement type: