A local, AI-assisted workspace for a repository's documentation and tickets. The Go backend serves the React app, the REST API, and proxies the CopilotKit AI runtime — everything on a single port.
# from any repository:
docker run --rm -it \
-e TARGET_UID="$(id -u)" \
-e TARGET_GID="$(id -g)" \
-v "$PWD:/workspace" \
-v devtop-ai-config:/etc/devtop \
-p 8000:8000 \
ghcr.io/synlace/devtop:latestOpen http://127.0.0.1:8000. Classic single-repo mode seeds ./.devtop/
(docs, tickets, threads) in your repo on first run.
Notes:
- Do not pass
-u "$(id -u):$(id -g)". The container starts as root, claims the config volume for$TARGET_UID:$TARGET_GID, then drops to that user (su-exec). That makes a freshdevtop-ai-configvolume writable without permissive modes. Files the app creates in the mounted repo are owned by your user. - The
devtop-ai-configvolume holds the instance's state: the AI key (/etc/devtop/.env,0600) and the registered-repo list (/etc/devtop/repos.json). The directory is0700, owned by$TARGET_UID:$TARGET_GID. - A host bind (
-v "$HOME/.config/devtop:/etc/devtop") is supported but not recommended: Docker re-creates a missing bind source as root, so persistence breaks if the directory was deleted after the mount point was decided. The named volume has no such race. - With no volume, the registry is held in the container's ephemeral config
dir and is lost on
docker rm.
# one instance, several repos (run from a parent folder):
docker run --rm -it \
-e TARGET_UID="$(id -u)" \
-e TARGET_GID="$(id -g)" \
-v "$PWD:/workspace" \
-v devtop-ai-config:/etc/devtop \
-p 8000:8000 \
ghcr.io/synlace/devtop:latestThe header chip lists every registered repo — pick one, or Manage repos… →
Add repo… to browse /workspace and register roots. Registered roots
persist in /etc/devtop/repos.json, so docker rm/docker run keeps your
repos. A fresh launch in a plain folder (no repo, no .git) boots with zero
repos and nothing written: the first-run page offers Add repo…, and
.devtop/ is scaffolded only when a repo is initialized.
One-time cleanup for existing installs. Older versions created
.devtop/in the workspace on every boot. A stale.devtop(even a partially-created one) is treated as a populated workspace, so the fresh folder-of-repos launch would seed it again. Delete the old directory once:rm -rf ~/git/.devtop— where~/gitis the folder you mount.
The AI key is entered through the UI only — never on the command line, so it
never appears in shell history or docker inspect:
- Open the chat panel → AI assistant is not configured → paste your OpenRouter key → Save.
- With the
devtop-ai-configvolume mounted, the key, base URL, and model are persisted as a 0600.envfile (/etc/devtop/.env) and remembered across restarts. The same.envformat is used in dev (repo-root.env). - Without the volume it is session-only (held in the runtime's memory, gone on restart). The panel explains how to add the volume.
- Use the key button in the chat header (or the settings panel) to remove the key at any time.
- CI/scripting:
docker run --env-file ~/.devtop.env ...(no key on the command line).
just devtop build # build the React app + devtop-bin
just devtop serve # run the built app + API on :8000
just devtop dev # dev loop: Vite HMR (:5173) + CopilotKit (:4000) + Go (:8000)
just devtop docker # build the image and run it against the current dir
just devtop image # build devtop:latest
just devtop run # docker run (existing image) against the current dir
just devtop push # build + push the image to the registry
just devtop init # create ./.devtop structure
just devtop clean # remove build artifacts and ./.devtopjust devtop dev runs on the host with no Docker: frontend edits hot-reload via
Vite; Go changes need a restart (the Go API serves only /api/*, so dev uses
the Vite dev server at :5173).
init materializes the complete scaffold once (UI Initialize on a repo, or
the classic-mode boot seed): dirs, config.yml, the default
agents/*.mdx, skills/*.mdx, and docs/index.mdx, all from the embedded
templates in templates/. Files already present are never overwritten — after
init the repo owns .devtop/ and it is the only thing devtop reads.
The default workflow ships as four agents:
docs— the chat agent (agent_runtime.default: docs): derives documentation from chat and the codebase.classify-doc— judges whether a doc is eligible for PRD derivation.prd-builder— derives a PRD (prds/) from an eligible doc.ticket-deriver— derives tickets from an approved PRD.
Plus default skills (ste100, acceptance-criteria, prd-review) and
copy-me examples in templates/examples/.
There are no built-in fallbacks: if an agent file is missing, derivation and
chat report the missing agent ("run init") instead of silently using defaults.
Edit or remove .devtop/agents/*.mdx to change the workflow; init re-adds
only files that do not exist.
- Go binary (
main.go):/api/*routes + the built React app fromfrontend/dist(SPA fallback toindex.html) + a reverse proxy for/api/copilotkit/*→ the Node runtime. - Docs are stored as raw Markdown + frontmatter and served as-is — the API
returns the file body, never rendered HTML (
renderMDis gone from the content endpoints). The source of truth is always the file on disk. - Rendering is client-side in the browser via the unified pipeline
(
frontend/src/RichMarkdown.tsx):react-markdown+remark-gfm(tables, task lists) +rehype-highlight(code). Mermaid diagrams are rendered by a lazily-loaded component (MermaidDiagram.tsx) so ~2MB of mermaid only loads when a doc actually contains a diagram. - CopilotKit runtime (
frontend/copilot-server.js): the AI chat, agent tools (docs, tickets, and workspace file read/list), thread persistence, and theai-status/ai-keyendpoints that power the UI key prompt. The key lives in this process; the volume is optional. The chat is always the active repo's default agent (.devtop/agents): the agent's body is the system prompt, itsmodeloverrides the default, and every tool call is authorized server-side against that agent's allowlist and permission scopes — no fallback, no unrestricted set. - The old server-rendered (Alpine) frontend and Go templates were removed — the React app is the only frontend.
just test ui # hermetic Playwright specs (no model calls)
just test ai # hermetic specs, then live @ai specs (paid model calls)