This project runs a local-first LangChain Deep Agent behind a Chainlit UI.
The app is wired for:
ChatOllamaorChatOpenAIwith a configurable local model backend- native Chainlit streaming for reasoning, tool calls, and final response
- config-driven synchronous and async DeepAgents subagents
- per-response download buttons for Markdown and PDF exports
- Postgres-backed LangGraph checkpoints and durable
/memories/whenDATABASE_URLis set - repo files mounted for the agent under
/workspace/
Set these variables before starting the app if you want environment-based overrides:
export DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/DBNAME?sslmode=disable"
export DEEPAGENT_MODEL_PROVIDER="ollama"
export DEEPAGENT_MODEL_BASE_URL="http://127.0.0.1:11434"
export DEEPAGENT_MODEL_NAME="gpt-oss:20b"
export DEEPAGENT_MODEL_REASONING="medium"
# export DEEPAGENT_MODEL_API_KEY="optional-for-secured-openai-compatible-servers"
export DEEPAGENT_CONFIG="deepagent.toml"
export CHAINLIT_AUTH_SECRET="replace-with-a-long-random-string"
export CHAINLIT_AUTH_USERNAME="admin"
export CHAINLIT_AUTH_PASSWORD="change-me"DATABASE_URL is optional now:
- when set, LangGraph checkpoints and
/memories/are persisted in Postgres - when unset, the app falls back to in-memory persistence for the current process only
DEEPAGENT_CONFIG is optional:
- defaults to
deepagent.tomlin the project root - if the file is missing, the app falls back to built-in model defaults and runs without extra skills, MCP servers, or custom subagents
DEEPAGENT_MODEL_* variables are optional:
- they override the matching
[model]values indeepagent.toml DEEPAGENT_MODEL_API_KEYis only needed for secured OpenAI-compatible serversOLLAMA_BASE_URL,OLLAMA_MODEL, andOLLAMA_REASONINGremain supported as Ollama-only compatibility aliases
CHAINLIT_AUTH_SECRET, CHAINLIT_AUTH_USERNAME, and CHAINLIT_AUTH_PASSWORD are optional:
- when all three are set, the app enables Chainlit password authentication
- together with
DATABASE_URL, that unlocks the native Chainlit history bar and chat resume UI - when they are unset, the app stays unauthenticated and the history bar remains unavailable
You only need Postgres if you want durable LangGraph checkpoints and /memories/.
If DATABASE_URL is unset, the app runs fully in memory for the current process.
This repo includes a Compose file for a local Postgres instance:
docker compose up -d postgresPoint the app at that database:
export DATABASE_URL="postgresql://chainagents:chainagents@127.0.0.1:5432/chainagents?sslmode=disable"Optional verification:
docker compose exec postgres psql -U chainagents -d chainagents -c "select 1;"Notes:
- The Compose file lives at compose.yaml and creates a persistent
postgres-datavolume. - If you already have Postgres installed locally, create an empty database and set
DATABASE_URLto that instance instead. - No separate migration step is required for this app. On startup it calls the LangGraph Postgres store and checkpointer
setup()routines automatically.
Chainlit only shows its built-in history sidebar when both persistence and authentication are enabled.
This app includes a simple password-based auth callback driven by environment variables:
export CHAINLIT_AUTH_SECRET="replace-with-a-long-random-string"
export CHAINLIT_AUTH_USERNAME="admin"
export CHAINLIT_AUTH_PASSWORD="change-me"With both DATABASE_URL and the CHAINLIT_AUTH_* variables set:
- users can sign in through Chainlit's native auth screen
- the history sidebar can list and reopen prior chats
- resumed chats default the LangGraph thread ID to the persisted Chainlit thread ID for that conversation
If you leave auth disabled, Chainlit can still persist thread records in Postgres, but the native history bar will stay hidden.
Install dependencies, then either pull an Ollama model or point deepagent.toml at an OpenAI-compatible server such as LM Studio:
uv sync
ollama pull gpt-oss:20bIf you are using LM Studio or another OpenAI-compatible server instead of Ollama, skip ollama pull, load a model in that server, and set [model].provider = "openai_compatible" with the server's base_url.
This repo now includes a live deepagent.toml with:
- model defaults for provider, base URL, model name, and reasoning effort
- a real
repoMCP server pinned tonpx @modelcontextprotocol/server-filesystem@2025.8.21 - a
repo-researchersubagent using prompts/repo-researcher.md - the repo-local
skills/source for both the main agent and the subagent
If the MCP package is not already cached on your machine, npx may download it on first use.
Start the Chainlit app:
chainlit run main.py -wYou can keep the model defaults in deepagent.toml:
[model]
provider = "ollama"
base_url = "http://127.0.0.1:11434"
temperature = 0
name = "gpt-oss:20b"
reasoning_effort = "medium"For LM Studio or another OpenAI-compatible server:
[model]
provider = "openai_compatible"
base_url = "http://127.0.0.1:1234/v1"
temperature = 0
name = "your-loaded-model-id"
reasoning_effort = "medium"
# api_key = "optional"Notes:
providerselectsChatOllamaorChatOpenAI.- Preferred shared fields are
base_url,name,temperature, andreasoning_effort. api_keyis optional and only used forprovider = "openai_compatible". When omitted, the runtime sends a placeholder token that local servers like LM Studio accept.- Legacy Ollama
endpointandportare still accepted whenprovider = "ollama"or omitted. reasoning_effortsets the default Chainlit reasoning level for new chats. Ollama uses that level directly; OpenAI-compatible servers may ignore it.DEEPAGENT_MODEL_PROVIDER,DEEPAGENT_MODEL_BASE_URL,DEEPAGENT_MODEL_NAME,DEEPAGENT_MODEL_API_KEY, andDEEPAGENT_MODEL_REASONINGoverride the TOML defaults when set.OLLAMA_BASE_URL,OLLAMA_MODEL, andOLLAMA_REASONINGstill work as Ollama-only compatibility aliases.
This repo also includes an app-specific chainlit.toml for UI behavior that the bridge owns:
[steps]
auto_collapse_delay_seconds = 3Notes:
chainlit.tomlis separate from Chainlit's native.chainlit/config.toml.[steps].auto_collapse_delay_secondscontrols how long completed reasoning and tool steps stay expanded before auto-collapsing.- If
chainlit.tomlis missing or invalid, the app falls back to3seconds.
The runtime now supports Deep Agents skill sources through deepagent.toml.
- Create a skill source directory in the repo, for example:
skills/
├── repo-docs/
│ └── SKILL.md
└── reviewer/
└── SKILL.md
- Add the source directory to
deepagent.toml:
[agent]
skills = ["skills"]Notes:
- Relative paths in
deepagent.tomlare resolved from the config file location. - Relative skill paths are automatically mapped into the Deep Agents virtual filesystem as
/workspace/.... - Each skill source directory should contain one or more skill folders, and each skill folder must contain
SKILL.md. - You can also use explicit virtual paths such as
"/workspace/skills/"if you prefer.
Minimal SKILL.md example:
---
name: reviewer
description: Use this skill when reviewing code changes for bugs and missing tests.
---
# reviewer
When asked to review code:
1. Read the relevant files first.
2. Focus on bugs, regressions, and missing tests.
3. Return concise findings with file references.Custom subagents are also loaded from deepagent.toml, and each subagent can have its own skills and mcp_servers.
Example:
[mcp]
tool_name_prefix = true
[mcp.servers.repo]
transport = "stdio"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem@2025.8.21", "."]
cwd = "."
[agent]
skills = ["skills"]
mcp_servers = ["repo"]
[[subagents]]
name = "repo-researcher"
description = "Researches the codebase and produces concise implementation guidance."
system_prompt_file = "prompts/repo-researcher.md"
skills = ["skills/research"]
mcp_servers = ["repo"]
[[subagents]]
name = "reviewer"
description = "Reviews proposed changes for bugs and regressions."
system_prompt = """
You are a strict code reviewer.
Focus on correctness, regressions, and missing tests.
Keep findings concise and actionable.
"""
skills = ["skills/reviewer"]
mcp_servers = ["repo"]
# model = "gpt-oss:20b"Supported subagent fields:
name: requireddescription: requiredsystem_promptorsystem_prompt_file: one is requiredskills: optional list of skill source paths for that subagentmcp_servers: optional list of MCP server names to attach to that subagentmodel: optional model override
Async subagents are loaded from deepagent.toml as background Agent Protocol jobs. They are useful for long-running or remote work where the main agent should return a task ID immediately and let you check, update, cancel, or list tasks later.
Example:
[[async_subagents]]
name = "remote-researcher"
description = "Runs longer research jobs in the background on an Agent Protocol server."
graph_id = "researcher"
# Omit url for ASGI transport in a co-deployed LangGraph setup.
# Set url for HTTP transport to a remote Agent Protocol server.
# url = "https://researcher-deployment.langsmith.dev"
# headers = { Authorization = "Bearer ${RESEARCHER_TOKEN}" }Supported async subagent fields:
name: requireddescription: requiredgraph_id: required graph or assistant ID on the Agent Protocol serverurl: optional remote Agent Protocol server URL; omit for ASGI transport in co-deployed LangGraph setupsheaders: optional request headers for remote/self-hosted Agent Protocol servers
For compatibility with DeepAgents' native discriminator, a [[subagents]] entry with a graph_id is also treated as an async subagent. Async subagents cannot define sync-only fields such as system_prompt, skills, mcp_servers, or model; those capabilities are configured on the remote graph.
This repo includes a LangGraph co-deployment entrypoint for ASGI transport:
- langgraph.json registers
supervisorandasync-researcher - langgraph_app.py exports both graphs
- omit
urlindeepagent.tomlwhen running through LangGraph Agent Server
Run the co-deployed Agent Protocol server with enough worker capacity for the supervisor plus background tasks:
uv run --with "langgraph-cli[inmem]" langgraph dev --n-jobs-per-worker 10ASGI transport is only available in this LangGraph server path. If you launch the UI with chainlit run main.py -w, use HTTP transport instead by setting url = "http://127.0.0.1:2024" on the async subagent.
Chainlit also starts a background notifier for launched async tasks. It polls the Agent Protocol server and posts a message when a task reaches success, error, cancelled, interrupted, or timeout. If deepagent.toml omits url for ASGI co-deployment, Chainlit defaults to http://127.0.0.1:2024, the usual langgraph dev URL. Override it with:
export CHAINLIT_ASYNC_SUBAGENT_URL="http://127.0.0.1:2024"Optional:
export CHAINLIT_ASYNC_TASK_POLL_SECONDS="5"MCP servers are defined once in deepagent.toml and then attached by name to the main agent or any subagent.
Example:
[mcp]
tool_name_prefix = true
[mcp.servers.repo]
transport = "stdio"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem@2025.8.21", "."]
cwd = "."
[mcp.servers.docs]
transport = "http"
url = "http://localhost:8000/mcp"
[mcp.servers.github]
transport = "sse"
url = "http://localhost:8080/sse"
headers = { Authorization = "Bearer ${GITHUB_TOKEN}" }
[agent]
mcp_servers = ["repo"]
[[subagents]]
name = "repo-researcher"
description = "Researches the repo and docs."
system_prompt = "Use the repo and docs MCP servers to answer questions."
mcp_servers = ["repo", "docs"]
[[subagents]]
name = "release-assistant"
description = "Works with repository metadata and hosted services."
system_prompt = "Use the GitHub MCP server when release metadata is needed."
mcp_servers = ["github"]Supported MCP config fields:
- top-level
[mcp] tool_name_prefix = true|false [mcp.servers.<name>] transport- for
stdio:command,args, optionalcwd, optionalenv - for
http,streamable_http,streamable-http:url, optionalheaders - for
sse:url, optionalheaders - for
websocket:url
Notes:
mcp_serverson[agent]attaches those MCP tools to the main agent.mcp_serverson[[subagents]]attaches those MCP tools only to that subagent.- Skills and MCP servers are independent. You can use neither, either, or both on any subagent.
- Relative
cwdvalues are resolved from the location ofdeepagent.toml. tool_name_prefix = trueis recommended when multiple MCP servers expose overlapping tool names.
Current scope of this config support:
- it supports Deep Agents built-in tool surface plus config-driven skills and MCP tools
- it supports config-driven sync subagents and async Agent Protocol subagents
- it does not yet provide a config-driven registry for custom Python tools per subagent beyond MCP
- if you need custom Python tools, extend deepagent_runtime.py
See deepagent.toml.example for a complete example.
/workspace/maps to this repo on disk./memories/is durable across LangGraph threads only whenDATABASE_URLis configured.- any other absolute path is treated as ephemeral scratch space by the deep agent backend.
- Native Chainlit history is available when both
DATABASE_URLand theCHAINLIT_AUTH_*variables are configured. - If
DATABASE_URLis set but authentication is not configured, Chainlit still persists thread records, but they are not browseable from the UI. - When
DATABASE_URLis unset, thread IDs only persist while the process stays alive. - When
DATABASE_URLis set, durable state is available through LangGraph thread IDs. You can reuse a thread ID from the chat settings panel to continue the same checkpointed thread. - On startup, the UI shows how many skill sources, MCP servers, custom subagents, and async subagents were loaded from
deepagent.toml.
This project is licensed under the MIT License. See LICENSE for the full text.