This hobby, personal project powers a highly configurable, local-first LangChain DeepAgent. We also provide interface to the agent though Chainlit, CLI and TUI.
Implemented features include:
- Sub-agents — delegate tasks to agents with isolated context windows
- Filesystem — read, write, edit, or search over pluggable local, sandboxed, or remote backends
- Context management — summarize long threads and offload tool outputs to disk
- Persistent memory — pluggable state and store backends for cross-session recall
- Skills — reusable behaviors the agent can load on demand
- Tools — bring your own functions or any MCP server
ChatOllama,ChatOpenAI, orChatAnthropicwith configurable model backends- native Chainlit streaming for reasoning, tool calls, and final response
- optional Langfuse tracing through the LangChain callback handler
- Chainlit image uploads sent to vision-capable models as photo attachments for OCR or image analysis
- Chainlit OCR/image uploads accept PNG, JPEG, WEBP, and GIF files
- 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/ - Chainlit Modes support for per-message reasoning selection (
Low,Medium,High)
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_ENDPOINT_URL="https://api.example.test/custom/v1/messages"
# export DEEPAGENT_MODEL_DISABLE_STREAMING_FOR_TOOL_CALLS="true"
export DEEPAGENT_MODEL_NAME="gpt-oss:20b"
export DEEPAGENT_MODEL_REASONING="medium"
export DEEPAGENT_RECURSION_LIMIT="200"
# export DEEPAGENT_MODEL_API_KEY="optional-for-secured-openai-compatible-servers"
# export ANTHROPIC_API_KEY="required-for-provider-anthropic-unless-DEEPAGENT_MODEL_API_KEY-is-set"
export DEEPAGENT_CONFIG="deepagent.toml"
export CHAINLIT_AUTH_SECRET="replace-with-a-long-random-string"
export CHAINLIT_AUTH_USERS='{"admin":"change-me","alice":"alice-password"}'
# export LANGFUSE_PUBLIC_KEY="pk-lf-..."
# export LANGFUSE_SECRET_KEY="sk-lf-..."
# export LANGFUSE_BASE_URL="https://cloud.langfuse.com"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
- if
[agent].state = "stateless", LangGraph checkpoint and store handles are not opened or passed to the agent graph even whenDATABASE_URLis set
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 used for secured OpenAI-compatible servers and can also supply the Anthropic API key whenANTHROPIC_API_KEYis unsetANTHROPIC_API_KEYis read first whenprovider = "anthropic"orprovider = "claude", so stale generic keys do not override the Claude credential- when switching to Anthropic with
DEEPAGENT_MODEL_PROVIDER, unset staleDEEPAGENT_MODEL_BASE_URL; useDEEPAGENT_MODEL_ENDPOINT_URLwith the/v1/messagespath for env-based Anthropic proxy switches, or pass--base-urlexplicitly from the CLI DEEPAGENT_MODEL_DISABLE_STREAMINGacceptstrue,false, ortool_calling;DEEPAGENT_MODEL_DISABLE_STREAMING_FOR_TOOL_CALLS=trueis a convenience alias fortool_callingOLLAMA_BASE_URL,OLLAMA_MODEL, andOLLAMA_REASONINGremain supported as Ollama-only compatibility aliases
DEEPAGENT_RECURSION_LIMIT is optional:
- it overrides
[agent].recursion_limitindeepagent.toml - it controls the maximum LangGraph steps for a single agent run
- raise it when long tool-heavy Deep Agent runs hit
GraphRecursionError
CHAINLIT_AUTH_SECRET and Chainlit user credentials are optional:
- when
CHAINLIT_AUTH_SECRETandCHAINLIT_AUTH_USERSare set, the app enables Chainlit password authentication for each configured user CHAINLIT_AUTH_USERSmust be a JSON object mapping usernames to passwords, e.g.{"admin":"change-me","alice":"alice-password"}- the legacy
CHAINLIT_AUTH_USERNAMEandCHAINLIT_AUTH_PASSWORDpair still works for a single user whenCHAINLIT_AUTH_USERSis unset - together with
DATABASE_URL, that unlocks the native Chainlit history bar and chat resume UI - when auth credentials 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/checkpointer
setup()routines and creates any missing Chainlit persistence tables ("User","Thread","Step","Feedback", and"Element") automatically. - If you manage the Chainlit schema externally, set
CHAINLIT_SCHEMA_BOOTSTRAP=falsebefore launching the app to skip the automatic Chainlit table bootstrap.
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_USERS='{"admin":"change-me","alice":"alice-password"}'For compatibility, a single user can still be configured with:
export CHAINLIT_AUTH_SECRET="replace-with-a-long-random-string"
export CHAINLIT_AUTH_USERNAME="admin"
export CHAINLIT_AUTH_PASSWORD="change-me"With DATABASE_URL, CHAINLIT_AUTH_SECRET, and either CHAINLIT_AUTH_USERS or the legacy username/password pair 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:20bPDF downloads are rendered with WeasyPrint. uv sync installs the Python package,
but WeasyPrint also needs native rendering libraries. On macOS, install them with:
brew install weasyprintOn Linux, install the Pango packages listed in the WeasyPrint installation guide for your distribution before starting the app.
If 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.
If you are using Claude through Anthropic, set [model].provider = "anthropic" and provide ANTHROPIC_API_KEY or DEEPAGENT_MODEL_API_KEY.
If you enable workspace-docs RAG with Ollama embeddings, also pull an embedding model such as:
ollama pull nomic-embed-textThis repo now includes a live deepagent.toml with:
- model defaults for provider, base URL, model name, and reasoning effort
- a higher LangGraph recursion limit for longer tool-heavy Deep Agent runs
- 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 -wStart the FastAPI server:
uv run chainagents-api --host 127.0.0.1 --port 8000The API uses the same deepagent.toml and environment settings as the Chainlit
and CLI entrypoints. Useful endpoints include:
curl http://127.0.0.1:8000/health
curl http://127.0.0.1:8000/api/status
THREAD_ID="api-$(uuidgen)"
curl -X POST http://127.0.0.1:8000/api/agent/invoke \
-H "Content-Type: application/json" \
-d "{\"prompt\":\"Summarize this repository\",\"thread_id\":\"$THREAD_ID\"}"
curl -N -X POST http://127.0.0.1:8000/api/agent/stream \
-H "Content-Type: application/json" \
-d "{\"prompt\":\"Summarize this repository\",\"thread_id\":\"$THREAD_ID\"}"Run the same underlying agent from a terminal without the Chainlit UI:
uv run chainagents --prompt "Summarize this repository" --thread-id cliStart the full-screen terminal UI:
uv run chainagents --tuiThe TUI defaults to thread ID tui, keeps the prompt box at the bottom, shows
the conversation in the main pane with Markdown-formatted assistant responses,
and splits reasoning and tool activity in the right sidebar. Type / in the TUI
prompt to show configured slash commands, and press Tab to complete the first
matching command. Stdio MCP server diagnostics are written to
.files/tui-stderr.log in TUI mode so they do not corrupt the full-screen
interface.
Useful CLI examples:
uv run chainagents --status --no-rag
uv run chainagents --configure
uv run chainagents --tui --reasoning high
uv run chainagents --list-commands
uv run chainagents --command ask-researcher --prompt "Find the config entrypoints"
uv run chainagents --stdin --model gemma4:26b --reasoning high < prompt.txt
uv run chainagents --rebuild-rag
uv run chainagents --upload-rag notes.md --prompt "Use my uploaded notes"
uv run chainagents --photo scene.jpg --prompt "Describe this photo"Run uv run chainagents --help for all runtime flags, including model provider,
base URL, endpoint URL, API key, temperature, persistence, MCP session scope,
async subagent URL, RAG controls, photo attachments, streaming, reasoning traces,
tool traces, and JSON output.
Core Python code lives under the chainagents/ package. The root-level Python
files are compatibility wrappers and entrypoints so existing imports and commands
continue to work.
chainagents/
runtime/ Core DeepAgents runtime, model setup, config parsing,
MCP/tool loading, persistence backends, and Langfuse.
interfaces/
chainlit/ Chainlit callbacks, UI bridge, auth, persistence,
uploads, async task notifications, and chat settings.
cli/ Terminal CLI parser, status output, command execution,
upload handling, and event rendering.
tui/ Full-screen Textual terminal UI.
api/ FastAPI application, request schemas, and streaming API.
events/ Shared LangGraph stream normalization used by all
interfaces.
commands/ Native slash-command parsing and dispatch helpers.
rag/ Workspace documentation RAG config, index, uploads,
and search tool.
exports/ Markdown and PDF response export helpers.
langgraph/ Agent Server graph exports.
util/ Shared utility helpers.
Runtime assets stay at the repository root because they are user/configuration content rather than importable Python package code:
deepagent.tomlanddeepagent.toml.example: model, agent, MCP, RAG, Chainlit, Langfuse, and subagent configuration.skills/: Deep Agents skill sources referenced from TOML asskills.prompts/: prompt files referenced by configured subagents.public/and.chainlit/: Chainlit static assets and native Chainlit config.tests/: regression tests for runtime, interfaces, RAG, exports, and events.
Compatibility wrappers such as main.py, deepagent_runtime.py,
chainlit_bridge.py, chainagents_cli.py, chainagents_api.py,
rag_runtime.py, and response_exports.py import the moved package modules.
Prefer new code under chainagents/, but keep the wrappers until external users
no longer rely on the old import paths.
You can keep the model defaults in deepagent.toml:
[model]
provider = "ollama"
base_url = "http://127.0.0.1:11434"
temperature = 0
repeat_penalty = 1.1
name = "gpt-oss:20b"
models = ["gpt-oss:20b", "gemma4:27b"]
reasoning_effort = "medium"
# Disable streaming only for requests that include tools, which can help
# model servers that emit malformed streamed tool-call chunks.
disable_streaming_for_tool_calls = falseFor 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"For OpenAI-compatible servers with a non-standard full chat-completions endpoint:
[model]
provider = "openai_compatible"
endpoint_url = "https://api.example.test/openai/deployments/local/chat/completions?api-version=2026-01-01"
name = "your-loaded-model-id"
# api_key = "optional"For Claude through Anthropic:
[model]
provider = "anthropic"
temperature = 0
name = "claude-sonnet-4-6"
models = ["claude-sonnet-4-6", "claude-opus-4-8", "claude-haiku-4-5-20251001"]
reasoning_effort = "medium"
thinking = "auto"
# api_key = "optional-if-ANTHROPIC_API_KEY-or-DEEPAGENT_MODEL_API_KEY-is-set"
# base_url = "https://api.anthropic.com"
# endpoint_url = "https://claude-proxy.example/proxy/v1/messages"Notes:
providerselectsChatOllama,ChatOpenAI, orChatAnthropic.provider = "claude"is accepted as an alias forprovider = "anthropic".- Preferred shared fields are
base_url,name,temperature, andreasoning_effort. repeat_penaltyis optional and currently applies toprovider = "ollama"; when omitted, Ollama defaults are used.disable_streaming = "tool_calling"ordisable_streaming_for_tool_calls = truebypasses model streaming only when tools are attached to the request; use this for providers that have trouble streaming tool-call chunks.disable_streaming = truedisables model streaming for all requests.endpoint_urlis an override for full non-standard model endpoint URLs. OpenAI-compatible paths ending in/chat/completionsor/responsesare normalized to the client base URL and query parameters are forwarded as OpenAI client default query parameters. Anthropic paths ending in/v1/messagesare normalized to the Claude client base URL and query parameters are forwarded as Anthropic client default query parameters.modelsis an optional list of model IDs surfaced in Chainlit settings and modes so users can switch models per session or per message.api_keyis optional forprovider = "openai_compatible"; when omitted, the runtime sends a placeholder token that local servers like LM Studio accept.- Anthropic requires an API key from
ANTHROPIC_API_KEY,DEEPAGENT_MODEL_API_KEY, orapi_key; when multiple are set,ANTHROPIC_API_KEYtakes precedence over the generic key. - When switching from another provider to Anthropic through environment or CLI overrides, provide Anthropic credentials through
ANTHROPIC_API_KEY,DEEPAGENT_MODEL_API_KEY, or--api-key; the runtime will not reuse anapi_keyfrom another provider's TOML config. - Legacy Ollama
endpointandportare still accepted whenprovider = "ollama"or omitted. reasoning_effortsets the default Chainlit reasoning level for new chats. Ollama uses that level directly, Anthropic maps it to Claudeeffort, and OpenAI-compatible servers may ignore it.thinkingcontrols Anthropic adaptive thinking:autoenables it only for known supported Claude models,adaptivealways sendsthinking = {"type": "adaptive"}, anddisablednever sends a thinking parameter.DEEPAGENT_MODEL_PROVIDER,DEEPAGENT_MODEL_BASE_URL,DEEPAGENT_MODEL_ENDPOINT_URL,DEEPAGENT_MODEL_NAME,DEEPAGENT_MODEL_API_KEY,DEEPAGENT_MODEL_REASONING,DEEPAGENT_MODEL_DISABLE_STREAMING, andDEEPAGENT_MODEL_DISABLE_STREAMING_FOR_TOOL_CALLSoverride the TOML defaults when set.OLLAMA_BASE_URL,OLLAMA_MODEL, andOLLAMA_REASONINGstill work as Ollama-only compatibility aliases.
Langfuse tracing is disabled by default. To enable it, set your Langfuse credentials in the environment and turn on the TOML option:
export LANGFUSE_PUBLIC_KEY="pk-lf-..."
export LANGFUSE_SECRET_KEY="sk-lf-..."
export LANGFUSE_BASE_URL="https://cloud.langfuse.com"[langfuse]
enabled = trueWhen enabled, ChainAgents attaches Langfuse's LangChain callback handler to Chainlit, CLI, TUI, and API agent runs. The LangGraph thread ID is also passed as the Langfuse session ID.
The [agent] table configures main-agent runtime behavior:
[agent]
state = "stateful"
recursion_limit = 200
memory_namespace = "filesystem"
memory_files = ["/memories/AGENTS.md"]
skills = ["skills"]
mcp_servers = ["repo"]
[agent.reflection]
enabled = true
memory_file = "/memories/AGENTS.md"
max_lesson_chars = 700
tool_failure_mode = "unrecovered"Notes:
state = "stateful"passes the configured LangGraph store and checkpointer to DeepAgents so thread IDs can continue conversation state.state = "stateless"omits those state handles and does not expose/memories/when building the agent graph.recursion_limitis the LangGraph step limit for one agent run.- The built-in default is
100; this repo'sdeepagent.tomlsets it to200. DEEPAGENT_RECURSION_LIMIToverrides this value when set.- Increase it for long tool-heavy runs that hit
GraphRecursionError; lower it if you want runaway loops to stop sooner. memory_namespaceis the shared agent-scoped StoreBackend namespace for/memories/. The default isfilesystemto preserve existing memory data from earlier configs. Use only letters, numbers, hyphens, underscores, dots,@,+, colons, and tildes.memory_fileslists/memories/files DeepAgents loads into the startup memory prompt. The default is["/memories/AGENTS.md"]; set it to[]to keep the memory route without startup memory loading.[agent.reflection]is opt-in. When enabled for stateful agents, ChainAgents proposes a compact lesson formemory_fileafter correction phrases such as "that was wrong" or after unrecovered tool failures. Chainlit asks with Save/Dismiss before writing through the agent; CLI, TUI, and API expose the proposal without mutating memory.
The app can build a local-first RAG index over repo documentation and expose it to the main agent as the search_workspace_knowledge tool.
Example config:
[rag]
enabled = true
persist_directory = ".rag"
include_globs = ["README.md", "chainlit.md", "prompts/**/*.md", "skills/**/*.md"]
exclude_globs = ["AGENTS.md", "AGENT.md"]
chunk_size = 1200
chunk_overlap = 200
top_k = 4
[rag.embedding]
provider = "auto"Notes:
- The default corpus is docs-only:
README.md,chainlit.md,prompts/**/*.md, andskills/**/*.md. AGENTS.mdandAGENT.mdstay out of RAG becauseAGENTS.mdis loaded directly into the main agent prompt when present.- The persisted local index lives under
.rag/and is safe to delete and rebuild. - With
provider = "auto", the embedding backend follows the active chat-model provider. - For Ollama, the default embedding model is
nomic-embed-text. - For OpenAI-compatible embeddings, set
[rag.embedding].modelexplicitly. - For Anthropic chat models, set
[rag.embedding].providerexplicitly toollamaoropenai_compatible;autocannot infer a Claude embedding backend. - On startup, the UI reports whether RAG is ready and how many files/chunks were indexed.
- The startup message includes a
Rebuild Knowledge Indexaction so you can refresh the index after documentation changes. - The startup message also includes
Upload File For RAG, which lets you add text-based files to the current chat thread's knowledge index. - Composer file attachments are enabled for text-based uploads; attached files are automatically ingested into the current thread's RAG store before the model responds.
- Uploaded files are thread-scoped and persist under
.rag/uploads/, so they do not leak into other chat threads.
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. - Every loaded skill is also exposed as a Chainlit slash command using the skill
name, for examplereviewerbecomes/reviewer. - Running a skill-backed slash command forces the main agent to read that skill's
SKILL.mdand apply it for that request. - Skills loaded through
[agent].skillsand sync[[subagents]].skillsare both considered for slash commands, but explicit[chainlit].commandstake precedence on name collisions.
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
stateful = true
[mcp.servers.repo]
transport = "stdio"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem@2025.8.21", "."]
cwd = "."
[agent]
state = "stateful"
recursion_limit = 200
skills = ["skills"]
mcp_servers = ["repo"]
summarization_trigger_tokens = 6000
summarization_keep_tokens = 2400
[[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
Main [agent] additions:
state: optional agent state mode. Usestatefulfor checkpointed conversation state, orstatelessto build the DeepAgents graph without a LangGraph store, checkpointer, or/memories/route. Defaults tostateful.recursion_limit: optional positive integer LangGraph step limit for a single agent run. Defaults to100unless overridden byDEEPAGENT_RECURSION_LIMIT.memory_namespace: optional non-empty namespace for agent-scoped/memories/storage. Defaults tofilesystem; allowed characters are letters, numbers,-,_,.,@,+,:, and~.memory_files: optional list of absolute/memories/file paths loaded into the DeepAgents startup memory prompt. Defaults to["/memories/AGENTS.md"]; use[]to disable startup memory loading.[agent.reflection]: optional correction-learning workflow.enabled = truerequiresstate = "stateful"and amemory_fileunder/memories/;max_lesson_charslimits proposal size;tool_failure_mode = "unrecovered"only proposes lessons for failed tool calls that do not produce a later final response.AGENTS.md: optional repo-root file that is automatically appended to the main/supervisor agent system prompt when present. It is not applied to separately configured async graph prompts.custom_instruction: optional string appended to the main/supervisor agent system prompt. This setting does not get applied to separately configured prompts such as theasync_researchergraph prompt.- DeepAgents provides its own summarization middleware in the main agent and sync subagents.
summarization_trigger_tokens: optional positive integer token threshold for DeepAgents' built-in summarization middleware.summarization_keep_tokens: optional positive integer token budget to keep after DeepAgents summarizes conversation history.- Legacy
summarization_middleware_enabledentries are still parsed for compatibility, but ChainAgents no longer injects a second summarization middleware.
You can configure slash-style commands that run from the Chainlit composer before the model call. Chainlit also auto-generates slash commands for loaded skills.
Place this config in deepagent.toml or whatever file DEEPAGENT_CONFIG points to.
Do not put it in the app UI file chainlit.toml or Chainlit's native .chainlit/config.toml.
Example:
[chainlit]
# Set false to hide model selection in chat settings and Modes.
model_mode_enabled = true
# Set false to disable per-message reasoning overrides from the Modes picker.
reasoning_mode_enabled = true
# Set false to hide streamed reasoning step panels and reasoning task entries.
reasoning_steps_enabled = true
# Set false to hide streamed tool step panels and tool task entries.
tool_steps_enabled = true
# Set false to hide the initial startup status message ("Workspace agent ready...").
startup_status_enabled = true
# Set false to keep legacy non-chronological streaming order in Chainlit.
chronological_ui_enabled = true
commands = [
{ name = "ask-researcher", description = "Delegate to repo-researcher.", target = "subagent", value = "repo-researcher", template = "{input}" },
{ name = "repo-readme", description = "Run an MCP tool directly.", target = "mcp_tool", value = "repo_read_file", mcp_server = "repo", template = "{\"path\":\"README.md\"}" },
{ name = "summarize", description = "Apply a prompt template.", target = "prompt", value = "Summarize the input", template = "Summarize this:\n{input}" }
]
starters = [
{ label = "Explain this repo", message = "Explain the architecture of this repository and identify the most important files.", command = "ask-researcher", icon = "book-open" },
{ label = "Review current changes", message = "Review the current working tree changes for bugs, regressions, and missing tests." }
]target modes:
prompt: rewrites the user prompt before sending it to the agent.subagent: rewrites the user prompt to direct the runtime to delegate via the configured subagent.mcp_tool: invokes the configured MCP tool directly and returns tool output in chat.
Notes:
- The
[chainlit]table for native commands belongs indeepagent.toml, alongside[model],[agent],[mcp],[[subagents]], and[[async_subagents]]. [chainlit].model_mode_enabled = falsehides the Model selector in chat settings and the Model mode group, and ignores per-message model overrides from UI modes.[chainlit].reasoning_mode_enabled = falsehides the Reasoning mode group and ignores per-message reasoning overrides from UI modes.[chainlit].reasoning_steps_enabled = falsehides streamed reasoningcl.Steppanels and reasoning task-list entries while preserving model reasoning settings.[chainlit].tool_steps_enabled = falsehides streamed toolcl.Steppanels and tool task-list entries while preserving tool execution.[chainlit].startup_status_enabled = falsedisables the initial startup status message that summarizes runtime configuration.[chainlit].chronological_ui_enabled = falsedisables chronological UI ordering so response tokens stream immediately and reasoning steps are not force-rolled at tool boundaries.- Command
nameis invoked as/<name>and must be unique. templateis optional and may include{input}.- For
mcp_tool, user command arguments must be valid JSON, e.g./repo-readme {"path":"README.md"}. - Each discovered skill also becomes
/<skill-name>automatically. For example, a skill withname: revieweris available as/reviewer. - Skill-backed commands always force the main agent to read the selected
SKILL.mdfirst and use it for that turn. - If a configured
[chainlit].commandsentry and a skill share the same slash name, the configured command wins. startersdefine starter prompts shown by Chainlit before the first message in a thread.- Starter
labelandmessageare required. Startercommandandiconare optional.
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
stateful = 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 - top-level
[mcp] stateful = 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.stateful = truekeeps MCP sessions open per LangGraph thread while the app process is running.stateful = falserecreates the MCP session for every tool call.
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 chainagents/runtime/core.py
See deepagent.toml.example for a complete example.
/workspace/maps to this repo on disk./memories/is available in stateful mode under the configured agent-scoped namespace and 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
DATABASE_URL,CHAINLIT_AUTH_SECRET, and eitherCHAINLIT_AUTH_USERSor the legacy username/password pair 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. - When
[agent].state = "stateless", thread IDs still identify requests and MCP/RAG scopes, but the agent graph does not checkpoint conversation state, receive a LangGraph store, or expose/memories/. - MCP stateful sessions are process-local. They survive tool calls in the same thread, but not an app restart.
- On startup, the UI shows how many skill sources, MCP servers, custom subagents, and async subagents were loaded from
deepagent.toml.
This hobby, personal project is made available under the MIT License and is built with open source Python libraries. See LICENSE for the full text.
