Skip to main content

LangStage

The web stage for your LangGraph agent. A chat workspace for LangGraph and deepagents agents — real-time streaming, a workspace file browser, scheduled runs, and a canvas for visualizations.

Renamed from cowork-dash (the old package name now just installs this one, and the cowork-dash command still works).

langstage — the web stage for your LangGraph agent

Stack: Python (FastAPI, chat over Server-Sent Events) backend, React (TypeScript + Vite) frontend.

Every stage for your LangGraph agent

langstage is the web stage (and namesake) of the LangStage family: write your agent once — any LangGraph CompiledGraph, from a single ReAct agent to a multi-agent supervisor — and run it on every stage with the same spec string (module:attr or path/to/file.py:attr), the same langstage.toml config file, and the same LANGSTAGE_* environment variables.

Multi-agent works out of the box. A supervisor, swarm, or crew compiles to the same CompiledStateGraph langstage loads, so its routing and hand-offs stream just like a single agent — no extra setup. See Running a multi-agent supervisor.

Stage Package Try it
Web app langstage you are here
JupyterLab langstage-jupyter pip install langstage-jupyter, then the chat sidebar in jupyter lab
Terminal langstage-cli langstage-cli -a my_agent.py:graph
VS Code langstage-vscode chat participant + stdio sidecar
Reference agent langstage-hermes LANGSTAGE_AGENT_SPEC=langstage_hermes.agent:graph on any stage
Shared core langstage-core typed events + config resolver behind every stage

Serve over AG-UI

This surface's agent — any LangGraph CompiledGraph — can also be served over the AG-UI protocol for use with AG-UI compatible clients:

pip install "langstage-core[agui]"
langstage-agui --agent my_agent.py:graph

📖 Full documentation: https://dkedar7.github.io/langstage-docs/

Features

  • Chat with real-time token streaming over Server-Sent Events (GET /api/stream + POST /api/chat)
  • Tool call visualization — inline display of arguments, results, duration, and status
  • Rich inline content — HTML, Plotly charts, images, DataFrames, PDFs, and JSON rendered directly in the chat
  • Canvas panel — persistent report surface for charts, tables, diagrams, images, and narrative markdown. Opt-in via CanvasMiddleware; auto-detected by the UI.
  • File browser — workspace file tree with syntax-highlighted viewer and live file change detection
  • Plan — sidebar todo list with progress bar, synced with agent write_todos calls (the Plan tab)
  • Async task board — delegate tasks to background copies of the agent and track them on a Kanban board (queued → ongoing → review → done); click a task to live-tail its stream, approve/reject human-in-the-loop pauses, and send follow-ups. The agent can also spawn its own async sub-tasks. See Task board.
  • Human-in-the-loop — interrupt dialog for reviewing and approving agent actions
  • Slash commands — /save-workflow, /create-workflow, and /run-workflow with autocomplete
  • Print / export — print conversations via browser Print dialog with optimized CSS
  • Token usage — cumulative counter with per-turn breakdown chart
  • Authentication — optional HTTP Basic Auth for all endpoints (except the health probe)
  • Health checks — GET /api/health (liveness, JSON, auth-exempt) and ?ready=1 (readiness: 200 only if the agent is a runnable graph and the task store is reachable, else 503) for reverse proxies, k8s, and uptime monitors
  • Theming — light, dark, and system-auto modes
  • Customization — title, subtitle, welcome message, agent name, and custom icon

Installation

pip install langstage

Quick Start

No agent or API key yet?

langstage run --demo

launches the full UI against a built-in keyless echo agent, so you can explore the surface before wiring up a real agent.

Note: running langstage run with no --demo and no --agent falls back to the built-in default agent, which requires the deepagents extra. Install it with pip install "langstage[deepagents]", or use --demo above for a zero-setup path that needs no extra install.

From Python

from langstage import CoworkApp

app = CoworkApp(
    agent=your_langgraph_agent,  # Any LangGraph CompiledGraph
    workspace="./workspace",
    title="My Agent",
)
app.run()

From CLI

# Point to a Python file exporting a LangGraph agent
langstage run --agent my_agent.py:agent --workspace ./workspace

# With options
langstage run --agent my_agent.py:agent --port 8080 --theme dark --title "My Agent"

Shorthand

from langstage import run_app

run_app(agent=your_agent, workspace="./workspace")

From a Jupyter notebook

The same app.run() works in a notebook — no threads, no nest_asyncio, no extra code. LangStage sees the kernel's running event loop and serves on a background thread, returning a handle immediately, so the cell doesn't block and the kernel stays interactive:

from langstage import CoworkApp

app = CoworkApp(agent=your_agent, workspace="./workspace")
server = app.run()      # -> LangStage running at http://localhost:8050

# ... keep using the notebook; the app is live in another tab ...

server.stop()           # shut it down when you're done

In a plain script or via the CLI, run() blocks exactly as it always has.

Working directory: in a script or via the CLI, run() makes the workspace the process working directory, so a bring-your-own agent's relative file writes land where the file browser shows them. In a notebook the kernel's working directory is left alone, so your own cells' relative paths keep resolving against the notebook's folder. The bundled agent's file tools use the absolute workspace path either way.

Enabling the Canvas

The canvas is opt-in. Attach CanvasMiddleware to your agent and the Canvas tab appears in the UI automatically:

from deepagents import create_deep_agent
from langstage import CoworkApp
from langstage.middleware import CanvasMiddleware

agent = create_deep_agent(
    tools=[...],
    middleware=[CanvasMiddleware()],   # <-- adds canvas tools + report guidance
    ...
)

CoworkApp(agent=agent, workspace="./workspace").run()

The middleware injects five tools (add_to_canvas, update_canvas_item, remove_canvas_item, add_canvas_section, reorder_canvas) and appends report-building instructions to the system prompt at each model call. Canvas items persist to .canvas/canvas.md in the workspace.

To force the tabs on/off regardless of middleware: --show-canvas/--no-show-canvas, --show-files/--no-show-files, or the Python-API show_canvas / show_files kwargs.

Bring your own agent

Point --agent at any compiled LangGraph graph — langstage run --agent my_agent.py:graph. Most of the UI works immediately; a few features light up when your agent follows a convention or carries a tool.

Works out of the box (no agent changes): chat with token streaming, tool-call visualization, the file browser, the task board (delegate any agent from the UI), and schedules.

Auto-handled: if your graph has no checkpointer, LangStage attaches an in-memory one so conversation memory, human-in-the-loop interrupts, and the task review gate work. Supply your own checkpointer for durability across restarts.

Unlock the rest:

Feature How
Plan tab populates agent calls write_todos (the deepagents convention)
Rich inline content (charts, images, DataFrames, HTML) a tool returns the display_inline shape
Canvas tab attach CanvasMiddleware to your agent
Agent self-delegation + agent-created schedules add the host tools — from langstage import LANGSTAGE_TOOLS → tools=[*my_tools, *LANGSTAGE_TOOLS]
Human-in-the-loop review use LangGraph interrupt() (or deepagents interrupt_on=...)

Preflight your agent with the built-in doctor — it loads your spec and reports exactly what will and won't light up:

langstage check --agent my_agent.py:graph

Without --agent, check and chat use the agent configured with LANGSTAGE_AGENT_SPEC or [agent] spec in langstage.toml, the same one run serves.

[ ok ] loads
[ ok ] checkpointer present (memory + interrupts + review gate)
[warn] no CanvasMiddleware - Canvas hidden (attach it to enable)
[ ok ] write_todos present - Plan tab will populate
[warn] async task tools not found - add `from langstage import LANGSTAGE_TOOLS` ...

The static checks are fast and need no API key. Add --live to also run one real turn and fail (exit 1) if the agent errors — a true CI readiness gate that catches a bad key or a tool that fails at runtime, which the static checks can't:

langstage check --agent my_agent.py:graph --live

Add --json for a machine-readable object (same exit codes) so CI can gate on any individual finding, not just the coarse pass/fail — for example, fail the build unless the agent loads and Canvas is wired:

langstage check --agent my_agent.py:graph --json \
  | jq -e '.loads and .checks.canvas.ok' > /dev/null

langstage config --json likewise emits the resolved config (each field's value + source, plus the TOML files read, including one that was found but is malformed), so a deploy step can assert a container resolved its env / langstage.toml the way it was meant to. Its issues list names everything that was ignored or degraded: a malformed langstage.toml, a malformed or invalid value (a bad port, an unknown theme), an unknown key. langstage config --strict exits 1 when that list isn't empty, so CI can gate on it.

Task board

The Board tab turns LangStage into a lightweight agent control room: delegate a task and it runs on a background copy of your agent while you keep chatting. No extra infrastructure — tasks are persisted in a local SQLite file (the board survives a restart) and executed by an in-process worker pool, built on the langstage-core task engine.

  • Delegate from the Board tab (or let the agent delegate to itself — see below). A task moves queued → ongoing → review → done; cancel or retry from any card.

  • Open a task (click its card) to live-tail the agent's full event stream — content and tool calls, rendered like the chat. Approve/reject a task paused for human review, or send it a follow-up.

  • Agent self-delegation — the default agent carries five tools (start_async_task, check_async_task, list_async_tasks, update_async_task, cancel_async_task) so it can spawn async sub-tasks; spawned tasks are linked to their parent on the board. Add them to a custom agent with:

    from langstage_core.tasks import TASK_TOOLS
    agent = create_deep_agent(tools=[*your_tools, *TASK_TOOLS], ...)
    
  • Scheduled runs (the Schedules tab) enqueue onto the same board, and are saved in the same SQLite file, so schedules survive a restart too. Fires missed while the server was down are not replayed; each schedule resumes at its next time.

  • Cron is interpreted in UTC. 0 9 * * * fires at 09:00 UTC — so scheduled runs are stable regardless of the host's timezone (and don't shift with DST) — and the Schedules tab shows next / last times in UTC to match, with the hour-specific presets labeled 9am UTC. next_run in GET /api/cron is already an explicit UTC timestamp (…+00:00).

  • A schedule never overlaps its own run. If the previous fire's task is still queued, running, or awaiting human review, the next automatic fire is skipped (the schedule row shows skipped: previous run still …) instead of piling up duplicate tasks. This matters when the scheduled agent has a human-in-the-loop gate — e.g. the default agent gates bash — because such a run parks at review on the board for you to approve, and the schedule waits for you rather than stacking stuck reviews. Manual Run now bypasses this. GET /api/cron surfaces each schedule's last_task_id + last_run_state so a client can flag one awaiting review.

Task REST API: GET /api/tasks, POST /api/tasks (delegate), GET /api/tasks/{id}/events, and POST /api/tasks/{id}/{cancel,retry,resume,message}. Concurrency is bounded by LANGSTAGE_TASK_CONCURRENCY (default 3). Every task runs the agent the server was started with: POST /api/tasks rejects a non-null agent_spec with 422 rather than loading another agent over REST.

Schedules (cron) REST API: GET /api/cron, POST /api/cron (create), DELETE /api/cron/{id}, and POST /api/cron/{id}/run (run now → enqueues a task). (The Schedules tab drives these; note the path is /api/cron, not /api/schedules.)

Single-process: run one server worker. The atomic task claim and the worker pool are scoped to one process; multiple uvicorn workers would double-run tasks.

Configuration

Configuration priority (highest wins): Python args > CLI args > environment variables > project langstage.toml (nearest at or above the working directory) > global ~/.langstage/config.toml > defaults.

There are two TOML layers, not one — both are read and deep-merged, with the project file winning key-by-key over the global file:

  • Project langstage.toml — the nearest langstage.toml found by walking up the directory tree from your current working directory (the way git finds .git). A file in a parent directory therefore configures every langstage run beneath it. Discovery is anchored to the working directory, not to --workspace: a langstage.toml sitting inside the workspace directory is not read unless that directory is also at or above your cwd.
  • Global ~/.langstage/config.toml — a per-user file applied to every invocation. Set LANGSTAGE_CONFIG_HOME to relocate the directory it lives in (the file is always named config.toml inside that directory). Legacy ~/.deepagents/config.toml and DEEPAGENTS_CONFIG_HOME are still honored as deprecated fallbacks, as are a project-level deepagents.toml and the DEEPAGENT_* env vars (the canonical LANGSTAGE_* names win when both are set).

Because a parent-directory or global file can silently set things like auth.password or server.host for every invocation under that tree, never assume config comes only from ./langstage.toml. When a value isn't what you expect, print the resolved configuration (each value, the source file it won from, and the env var / langstage.toml key that sets it):

langstage --show-config

Scaffold a config file with the inverse command. langstage init writes a fully-commented langstage.toml — every option present but commented out, grouped into its TOML section and annotated with its env-var equivalent — so you never have to guess the section nesting:

langstage init                 # write ./langstage.toml (refuses if it exists)
langstage init --force         # overwrite
langstage init --path ./cfg/   # target a directory or file

Only a file named langstage.toml at or above the directory you run langstage from is discovered (see above), so init --path ./cfg/ writes a file that is read when you run from cfg/, not from the current directory. init prints a note when the file it wrote won't be discovered from where you ran it. Keys whose default is unset or auto (agent.spec, ui.show_canvas, ui.show_files) are shown with an example value and marked example on the line; leave them commented to keep the default.

init is generated from the same field table config reads, so the two stay in lockstep — a config → init → config round-trip is exact.

Option CLI Flag Env Var Default
Agent spec --agent LANGSTAGE_AGENT_SPEC Built-in default agent (requires deepagents extra — see Quick Start)
Workspace --workspace LANGSTAGE_WORKSPACE_ROOT .
Host --host LANGSTAGE_HOST localhost
Port --port LANGSTAGE_PORT 8050
Debug --debug LANGSTAGE_DEBUG false
Title --title LANGSTAGE_TITLE Agent's .name or "LangStage"
Subtitle --subtitle LANGSTAGE_SUBTITLE "" (hidden when unset)
Welcome message --welcome-message LANGSTAGE_WELCOME_MESSAGE (empty)
Theme --theme LANGSTAGE_THEME auto
Agent name --agent-name LANGSTAGE_AGENT_NAME Agent's .name or "Agent"
Icon URL --icon-url LANGSTAGE_ICON_URL (none)
Auth username --auth-username LANGSTAGE_AUTH_USERNAME admin
Auth password --auth-password LANGSTAGE_AUTH_PASSWORD (none — auth disabled)
Save workflow prompt --save-workflow-prompt LANGSTAGE_SAVE_WORKFLOW_PROMPT (built-in)
Run workflow prompt --run-workflow-prompt LANGSTAGE_RUN_WORKFLOW_PROMPT (built-in, use {filename})
Create workflow prompt --create-workflow-prompt LANGSTAGE_CREATE_WORKFLOW_PROMPT (built-in)
Custom CSS --custom-css LANGSTAGE_CUSTOM_CSS (none) — see Custom CSS Theming
Show Canvas tab --show-canvas/--no-show-canvas LANGSTAGE_SHOW_CANVAS Auto — on when CanvasMiddleware is attached
Show Files tab --show-files/--no-show-files LANGSTAGE_SHOW_FILES true
Task concurrency (env / langstage.toml only) LANGSTAGE_TASK_CONCURRENCY 3
CORS origins (env / langstage.toml [server] cors_origins only) LANGSTAGE_CORS_ORIGINS (empty — loopback origins only). Comma-separated origins granted credentialed cross-origin access; * allows any origin with credentials off

Exposing the server to the network? The default localhost bind is reachable only from the same machine. If you bind a non-loopback host (--host 0.0.0.0, or a concrete LAN address) to reach it from elsewhere, set --auth-password (or LANGSTAGE_AUTH_PASSWORD) — otherwise the entire REST surface (chat, the workspace file browser with read/write/delete/upload, and the task board) is reachable, unauthenticated, by anyone on the network. LangStage prints a startup warning in that case but still starts; the safest alternative is to keep the localhost bind and reach it over an SSH tunnel.

Slash Commands

Type / in the chat input to access built-in commands:

Command Description
/save-workflow Capture the current conversation as a reusable workflow in ./workflows/
/create-workflow Create a new workflow from scratch — prompts for a topic description
/run-workflow Execute a saved workflow — shows an autocomplete dropdown of .md files from ./workflows/

All commands support inline arguments:

/save-workflow focus on the data cleaning steps
/create-workflow daily sales report pipeline
/run-workflow etl-pipeline.md skip step 3

The prompt templates behind each command are configurable via Python API, CLI flags, or environment variables (see Configuration table above).

Stream Parser Config

Control how agent events are parsed by passing stream_parser_config to CoworkApp:

app = CoworkApp(
    agent=agent,
    stream_parser_config={
        "extractors": [...],  # Custom tool extractors
    },
)

See langstage-core for details.

Custom CSS Theming

Override the UI's color scheme with your own CSS file. LangStage exposes its theme as CSS custom properties, so you can restyle without fighting specificity:

/* theme.css */
:root {
  --color-primary: #0077b6;
  --color-surface: #f8fbff;
}

.dark {
  --color-surface: #0a1628;
}

Point LangStage at the file via --custom-css, LANGSTAGE_CUSTOM_CSS, or custom_css in langstage.toml:

langstage run --demo --custom-css ./theme.css

The file is served at /api/custom-css and injected into the UI. When set via --custom-css, the path must exist (validated at startup); when set via the env var, config file, or Python API, a missing file just logs a warning and the default theme is used instead.

Troubleshooting

Files the agent creates don't show up in the file browser

If the agent's bash/file tools appear to write somewhere other than the workspace shown in the UI's file browser, you're likely on langstage < 0.12.2. Before that version, --workspace, langstage.toml's [workspace] root, and the Python workspace= kwarg only configured the file browser — the agent's own tools kept running in whatever directory the server was launched from, so files the agent wrote would silently land outside the workspace and never appear in the browser.

Fix: pip install --upgrade langstage (0.12.2+ threads the resolved workspace into the agent's tools too, not just the file browser).

Verify it's working: ask the agent to run pwd and confirm the reported path matches your configured workspace. If you're not ready to upgrade, LANGSTAGE_WORKSPACE_ROOT reached the agent's tools correctly even on the affected versions, so setting it directly is a safe fallback.

Architecture

Browser  <--SSE / REST-->  FastAPI  <--astream_events-->  LangGraph Agent
                              |
   chat (Server-Sent Events):  GET /api/stream?session_id=...   (event stream)
                               POST /api/chat {session_id, content}
   other REST APIs:            /api/config
                               /api/files/tree
                               /api/files/read?path=...   (also: preview, download, upload, mkdir, delete)
                               /api/canvas/items
                               /api/cron        (schedules)
                               /api/tasks       (async task board)

The frontend is pre-built and bundled into the Python package as static files. No Node.js required at runtime.

REST API

Because the backend is FastAPI, LangStage serves a complete, always-in-sync OpenAPI schema for the whole REST surface (chat, files, canvas, cron, tasks, health) — use it as the canonical reference for a programmatic client instead of reverse-engineering shapes:

Route What
/docs Interactive Swagger UI — try every endpoint, see exact request/response schemas
/redoc ReDoc — a clean, readable reference of the same schema
/openapi.json The raw OpenAPI document — feed it to a client generator (openapi-generator, etc.)

All three honor auth: with --auth-password set they return 401 without credentials (like every route except /api/health). A couple of shapes worth knowing (and that /docs spells out): POST /api/files/upload takes path as a query parameter (not a form field), and path is the full destination path — upload?path=P stores the file at P, so it round-trips with read/download/delete?path=P (end path with /, or point it at an existing directory, to drop the upload inside under its own filename instead); /api/stream is the SSE event stream keyed by session_id.

Development

# Backend
pip install -e ".[dev]"
pytest tests/

# Frontend
cd frontend
npm install
npm run build    # outputs to langstage/static/
npm run dev      # dev server with hot reload (proxy to backend on :8050)

License

MIT

Release files for langstage 0.13.40

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for langstage 0.13.40
File Size Uploaded
langstage-0.13.40.tar.gz 2.2 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for langstage 0.13.40
File Interpreter ABI Platform
langstage-0.13.40-py3-none-any.whl Python 3 none any Details

Total release size: 3.4 MB

Release files / langstage-0.13.40.tar.gz

Download URL langstage-0.13.40.tar.gz
Size 2.2 MB
Tags Source
SHA-256 checksum
How to use checksums
8ae14fcda5c3b77e6eaf858ef81b16fc5808eeb2fdbb59fa1eacc336cf25db5e
BLAKE2b-256 checksum
How to use checksums
b97b53205c4e500c6b265a753518bef24ba0910152b70c34ee61ad9e53bb71c8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / langstage-0.13.40-py3-none-any.whl

Download URL langstage-0.13.40-py3-none-any.whl
Size 1.2 MB
Tags Python 3
SHA-256 checksum
How to use checksums
a864b880107d6e6f1264a2ee7841f36caa1300276552eadc5b0a29b21da80dc3
BLAKE2b-256 checksum
How to use checksums
5bb6549c36d849c9d52ff287a9113c6d041cdf8f930b7ce6f7848f043641f657
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.13.40 This release

2 release files

0.12.2

2 release files

0.12.1

2 release files

0.12.0

2 release files

0.11.9

2 release files

0.11.8

2 release files

0.11.7

2 release files

0.11.6

2 release files

0.11.5

2 release files

0.11.4

2 release files

0.11.3

2 release files

0.11.2

2 release files

0.11.1

2 release files

0.11.0

2 release files

0.10.0

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page