Local-first, trauma-informed AI assistant for people navigating re-entry after incarceration.
Built with deepagents, LangGraph, and Claude.
- Python 3.13 (pinned in
.python-version) uv(Python package manager)- An Anthropic API key (subagents + form filler)
- A Google AI API key (orchestrator uses Gemini)
- An xAI API key (housing subagent uses Grok)
- A Browserbase account (form filler remote browser — free tier available)
# Clone the repo
git clone https://github.com/skylarrwang/threshold.git
cd threshold
# Install dependencies (uv handles virtualenv automatically)
uv sync
# Copy env template and add your API key
cp .env.example .env
# Edit .env and set your API keys (see Environment Variables below)# 1. Create a test profile (generates encryption key + demo user "Tyler")
uv run python main.py seed
# 2. Start chatting
uv run python main.py chat
# 3. View current profile
uv run python main.py profileInside the chat, type help for available commands and capabilities.
If you have LangGraph Studio, you can run the agent with a visual debugger:
langgraph devThe graph config is in langgraph.json.
threshold/
├── agents/
│ ├── orchestrator.py # Main agent — routes to tools and subagents
│ └── subagents/
│ ├── benefits.py # Benefits enrollment specialist subagent
│ ├── employment.py # Employment specialist subagent
│ ├── form_filler.py # Computer-use form filler (Browserbase + Claude)
│ ├── housing.py # Housing specialist subagent
│ └── legal.py # Supervision & documents specialist subagent
├── memory/
│ ├── encryption.py # AES-256 profile encryption (Fernet)
│ ├── profile.py # UserProfile Pydantic model + persistence
│ ├── observation_stream.py # Rolling event/observation log
│ └── reflection.py # Memory synthesis and reflections
├── tools/
│ ├── crisis_response.py # Crisis hotline / safety tool
│ ├── memory_tools.py # read_user_memory, update_profile_field, log_event
│ ├── benefits_lookup.py # SNAP, Medicaid, SSI eligibility checks
│ ├── supervision_tracker.py # Parole/probation condition tracking
│ ├── document_lookup.py # ID restoration, expungement eligibility
│ ├── job_search.py # ⚠️ STUB — job search + ban-the-box
│ ├── housing_search.py # ⚠️ STUB — housing search
│ └── form_filler/ # Computer-use form filling
│ ├── browser.py # Browserbase remote browser session
│ ├── loop.py # Agentic screenshot→action loop
│ ├── safety.py # URL allowlist + profile field redaction
│ └── types.py # FormFillRequest/Result models
└── tests/
workflows/
├── cover_letter.md # Step-by-step cover letter workflow
├── resume.md # Resume building workflow
├── housing_application_letter.md
├── legal_letter.md
└── community_resource_search.md
main.py # CLI entrypoint (Typer + Rich)
AGENTS.md # Agent long-term memory (auto-updated)
langgraph.json # LangGraph deployment config
pyproject.toml # Dependencies (managed by uv)
The core agent scaffolding is in place. Your main job is to replace the stub tools with real API integrations and improve/add workflow files.
There are two files with stub implementations that return mock data. Each one has TODO comments at the top explaining exactly what API to integrate.
What it does now: Returns hardcoded MOCK_JOBS list (5 sample jobs).
What needs to happen:
- Replace
search_jobs()with a real job API call. TheTODOat line 19 suggests Adzuna API. Other options: Indeed API, Google Jobs API, or JSearch on RapidAPI. - Filter/prioritize results for ban-the-box and second-chance employers.
log_job_application()andget_ban_the_box_status()are already fully implemented — no changes needed there.
Key constraint: The function signatures and return types must stay the same (they're @tool-decorated functions that return str). The orchestrator and employment subagent call these by name.
What it does now: Returns hardcoded MOCK_HOUSING list (5 sample programs).
What needs to happen:
- Replace
search_housing()with a real data source. TheTODOat line 14 suggests 211.org API or web scraping. Other options: HUD API, Reentry Housing Directory scraping. log_housing_application()is fully implemented — no changes needed.
Key constraint: Same as above — keep the @tool decorator, function signature, and str return type.
Every tool is a function decorated with @tool from langchain_core.tools. The agent calls them by name based on the user's request.
-
Adding a new tool: Define the function in the appropriate file under
threshold/tools/, add@tool, and then:- Export it from
threshold/tools/__init__.py - Add it to the relevant tool list in
threshold/agents/orchestrator.py(for core tools) or the subagent definition inthreshold/agents/subagents/employment.py/housing.py - Mention it in the system prompt so the agent knows when to use it
- Export it from
-
Tool return format: Always return a
str. Use markdown formatting for readability — the agent passes the return value directly to the user.
Subagents are defined as plain Python dicts in threshold/agents/subagents/. Each has:
name— how the orchestrator references it viatask()description— tells the orchestrator when to delegatesystem_prompt— instructions for the subagenttools— list of tool functions it can callmodel— which model to use (can be any LangChain chat model)
The form-filler subagent is special — it's a CompiledSubAgent with a custom LangGraph graph wrapping the computer-use agentic loop.
The orchestrator delegates to subagents using the built-in task() tool from deepagents. You don't need to wire up routing — the orchestrator decides based on the description.
Current model assignments:
| Subagent | Provider | Model |
|---|---|---|
| Orchestrator | Google Gemini | gemini-2.5-flash |
| Benefits | Anthropic | claude-haiku-4-5-20251001 |
| Employment | Anthropic | claude-sonnet-4-6 |
| Housing | xAI | grok-4-1-fast |
| Legal | Anthropic | claude-haiku-4-5-20251001 |
| Form Filler | Anthropic | claude-sonnet-4-6 (computer use) |
Files in workflows/ are step-by-step markdown instructions that agents read at runtime using read_file(). The agent is told to read the relevant workflow before performing writing tasks (cover letters, resumes, etc.).
To add a new workflow:
- Create a
.mdfile inworkflows/ - Reference it in the orchestrator system prompt (
threshold/agents/orchestrator.py, around line 66) - If it's subagent-specific, also reference it in that subagent's system prompt
These documents contain the full design spec and architecture. Read them if you need context on why something is built a certain way:
first_steps.md— Implementation plan with architecture decisionsthreshold_impl.md— Full design document with all schemas, flows, and rationale
| Variable | Required | Description |
|---|---|---|
ANTHROPIC_API_KEY |
Yes | Anthropic API key for Claude (subagents + form filler) |
GOOGLE_API_KEY |
Yes | Google AI API key (orchestrator uses Gemini) |
XAI_API_KEY |
Yes | xAI API key (housing subagent uses Grok) |
BROWSER_BASE_API |
Yes | Browserbase API key (for form filler remote browser) |
BROWSER_BASE_PROJECT_ID |
Yes | Browserbase project ID |
THRESHOLD_DATA_DIR |
No | Data directory (default: ./data) |
THRESHOLD_ENCRYPTION_KEY |
Auto | Fernet key for profile encryption (auto-generated by seed) |
THRESHOLD_MODEL |
No | Model for orchestrator (default: gemini-2.5-flash) |
THRESHOLD_INTERVIEW_MODEL |
No | Model for interview agent (default: claude-sonnet-4-6) |
LANGCHAIN_TRACING_V2 |
No | Enable LangSmith tracing (true/false) |
LANGCHAIN_API_KEY |
No | LangSmith API key (only if tracing enabled) |
# Run form filler unit tests (no API keys needed — uses mocks)
uv run python -m unittest discover -s threshold/tests -p 'test_form_filler*.py'Tests cover key normalization, URL allowlisting, profile field redaction, the agentic loop, and subagent behavior.
The form-filler subagent can fill out online government forms using Anthropic's computer use tool and a remote Browserbase browser. When triggered:
- A remote browser session is created on Browserbase
- The agent prints a live view URL — open it in your browser to watch in real-time
- Claude (Sonnet) navigates the form, reading screenshots and filling fields from the user's profile
- The agent never clicks submit — the user reviews and submits manually via the live view
Safety: Only .gov URLs are allowed. Sensitive profile fields (offense category, supervision details) are automatically redacted before being sent to any form.
Usage: Ask the agent to fill a form and include the URL, e.g.:
Help me fill out the CT DMV appointment form at https://dmv.service.ct.gov/...
- Interview agent — guided onboarding conversation to build the user profile (currently bypassed with
seed) - Real job/housing APIs — currently returns mock data (see above)
- Frontend — design spec exists in
threshold_frontend_design.md