An on-device AI coding editor with enforced guardrails and receipts. At its core it is a customized Eclipse Theia editor with an AI coding agent built in as a native panel that lives inside the editor - the agent is not docked next to a separate window the way it works in other tools. It plans, reads, writes and runs code against your actual workspace, right where you edit.
Local by default: your code, the editor, and the agent "brain" (the model orchestration) run on your machine. Point it at a model on your own machine and nothing leaves it. Or bring your own key (BYOK) for a cloud model you trust, and only the prompt the agent assembled goes there: the request, the conversation context, and the file contents it read.
The project began as two decoupled halves: the agent capability is from Eigent, while the editor shell + on-device packaging here are our own. Both are fused here into a single editor-first product - the "Antigravity" model.
undisclosed-demo.mp4
Watch the 33-second demo (1920x1080, mp4) if your
viewer doesn't play it inline. The demo video was cut, captioned and rendered
inside Undisclosed itself, with the agent driving the HyperFrames pipeline. The
task is the dispatchkit work sample: three bugs in a three-file webhook delivery
worker. The agent reads the tests and the modules, names the three bugs, fixes
them in place, then runs pytest and all thirteen pass. It shows the loop end to
end, not the ceiling.
Vertical cut for TikTok, Reels and Shorts (1080x1920, 32s): undisclosed-demo-9x16.mp4.
- Agent-native editor shell built on Eclipse Theia (not a fork of VS Code), 100% open-source (Apache-2.0). The agent panel (a React app) is served and mounted as a first-class dock widget by a Theia extension.
- It can actually touch your workspace: terminal, file read/write, git, search, debugging, browser + screenshots, web search - via selectable "hands". The agent plans in your editor and executes changes you can see and review.
- Runs on-device. The "brain" is a local FastAPI (uvicorn) agent backend,
bound to
127.0.0.1:5001by default. No SaaS runtime, no vendor cloud. Model calls use the keys you configure (OpenAI, Gemini, and more). - Verifiable controls, not a promise: an approval gate for risky tools, on by default (risky calls wait for your approval; always-approve is opt-in), a memory switch with Clear, opt-in debug dumps, and localhost-only networking by default. See Privacy and control.
- Checks before "done": typecheck + lint, a deterministic diff review, and tests paired with a coverage-gap plan. See Reliability over tool count.
- Storybook + hot reload for fast agent-UI iteration, separate from the slow full editor rebuild.
- Installable desktop app (Electron + frozen Python brain) for macOS / Windows / Linux, plus a ready-to-package release pipeline.
- Extensible: Monaco/Monarch custom languages, VS Code settings + extension import, git extras, and a skill system for the agent (20 skills bundled, 6 enabled on a new install, the rest one toggle away).
The shape question changed. Cline now ships a CLI and a desktop app, and OpenCode has a desktop beta, so "extension vs terminal" no longer separates the field. What still separates it is whether the editor is open, whether the model can run on your machine, and whether a change reaches you as a diff you approve.
| What it is today | What you own, and what you give up | |
|---|---|---|
| Cline | Extension for VS Code and JetBrains, a CLI, and a desktop agent app | Open source, local models or your own key. In the editor it is still a guest inside the host's extension API |
| OpenCode | Terminal UI, plus a desktop app in beta | Open source, local models or your own key. Fast and scriptable; diffs are reviewed in the TUI or by opening an editor |
| Cursor | Closed-source VS Code fork | Polished and account-bound. The vendor is in the model path by default, and the code that reads your code is not open |
| Undisclosed | The editor itself: Theia with the agent docked | Apache-2.0, a local model or your own key, and diffs, terminal, git and the test receipt in one window. Newer and smaller, and the macOS build is unsigned |
Checked 20 October 2026 against each project's own README. This category moves fast; if a row is out of date, open an issue and we will correct it.
Two local processes keep the editor and the agent separate:
| Process | What it is | Listens on |
|---|---|---|
| Editor | Theia + the undisclosed-* extensions + the embedded agent UI |
:3000 (dev, browser) |
| Brain | FastAPI / uvicorn agent backend (model routing, toolkits/hands, memory, SSE streaming) | :5001 |
The agent UI (agent-ui/) is a React + Vite app. A self-contained UMD module is built
and copied into the undisclosed-agent Theia extension, which serves it and mounts it
in the right-side panel. In development the brain runs as its own process
(scripts/brain.sh); in the packaged desktop app it is frozen with PyInstaller and
auto-launched alongside Electron.
┌───────────────────────────────────────────────┐ ┌───────────────────────────────┐
│ Editor surface (Theia + undisclosed-agent) │ │ Brain (FastAPI, :5001) │
│ · editor / terminal / git / file explorer │ │ · model routing/streaming │
│ · agent panel = native dock widget (React) │◄───►│ · hands: shell, files, git, │
│ ┌──────────┐ │/api │ browser, search, skills │
│ │ Agent UI │ ── /api & SSE ─────────────┴────►│ · memory (vector) + history │
└──────┴──────────┴─────────────────────────────┘ └───────────────────────────────┘
In dev the browser proxies /api on :3000 → brain; in the packaged app the
Theia/Electron backend proxies /api straight to the bundled brain.
"Local-first" is only worth something if you can check it. These are the concrete answers for this build, with the code that implements each one.
Only when you choose a cloud model.
- Local model: if the model runs on your machine (Ollama, LM Studio, Hugging Face, or any OpenAI-compatible server on localhost), the model call stays on the machine. No Undisclosed server is in the path. Local coding is still behind cloud on long, multi-file work, so use the largest coding model your machine can hold (14B and up if it fits) and expect to review more of the diff.
- Cloud model (BYOK): the provider you configured receives the request the agent assembled: the prompt, the conversation context, file contents the agent read, and tool results it is reasoning over. The editor and brain still run locally; the key goes from this machine to that provider, not through us.
- Other hands have their own network paths: web search, the CDP browser, GitHub, Figma, Notion, Slack, Google Workspace and similar toolkits talk to their own services when you connect them. Each one stays unavailable until its credentials are configured.
There is no Undisclosed account and we do not ship an analytics SDK in the app. The minimum network requirement is the model endpoint you configure.
Everything is on disk under your home directory:
| Path | What it holds |
|---|---|
~/.undisclosed/turns/ |
conversation turns (turn_<id>.json): messages, tool activity summaries, token counts |
~/.undisclosed/memory/ |
the memory tree: facts, conversation events, tool events, run summaries, and the local vector store |
~/.undisclosed/logs/brain.log |
packaged brain stdout/stderr, rotated at 16 MB |
~/.undisclosed/debug/<task>.log |
full turn dumps, only when UNDISCLOSED_DEBUG=1: prompt, tool calls with arguments and results, model response |
~/eigent/ (falls back to ~/.undisclosed/) |
per-task run artifacts and the working files the brain writes |
Memory is on by default. The panel's Memory screen has a "Remember across
conversations" switch and a Clear button (GET /memory/status, DELETE /memory).
Turning it off stops new facts for new tasks; Clear removes the stored facts.
Debug dumps are off unless you set the flag.
No, not by default. Ask is the default mode: mutating calls wait for an explicit approval in the panel before they execute. Auto (always approve) is opt-in from the panel's mode menu or Settings > Privacy, and the choice is remembered per user.
In Ask mode these wait for approval:
shell_exec(terminal)write_to_fileandapply_patchexecute_code(worker agents)- git writes (
git_add,git_commit,git_push,git_checkout,git_stash,git_create_branch) - GitHub writes (issues, pull requests, comments, branches)
- MongoDB's write-capable
mongo_evalescape hatch
The prompt names the tool, its arguments and the workspace (built by
_describe_call in
governance_toolkit.py).
Denying it, or not answering within five minutes, returns a governance rejection
to the model instead of running the action. Read-only calls, including
read-only shell commands, are not gated.
Commands are classified per segment by
command_policy.py. A hard-blocked
command (rm, sudo, dd, mkfs, find -delete, xargs rm) is refused in
both modes and approval cannot override it. The destructive tier (git push,
git reset, git clean, GitHub writes) prompts in both modes, Auto included.
A compound command is judged by its riskiest part. The mode is sent with every
turn as toolkit_config.governance.mode; the gate wiring is in
toolkit_assembler.py and the
UI side in chatStore.ts.
This is static analysis of the command line, not a sandbox. Two limits follow
from that: the contents of a script file are not read before it runs, and inline
code that can hand the command to another process (os.system, subprocess,
child_process, __import__) is tier 2, so it prompts in every mode. A literal
delete target outside the workspace is tier 3; a target the classifier cannot
resolve statically is tier 2.
Integrations are credential-gated: the toolkit is only assembled when its token,
OAuth grant, or MCP server is present. Ask mode (the default) gates local file
writes, the MongoDB escape hatch and git add/commit. GitHub writes and the
destructive git actions (push, reset, clean) prompt in every mode, Auto
included.
- Ask mode (the default) shows the exact pending call before it runs.
- The agent plans in the panel, and file changes show up as diffs you can review in the panel and in git.
UNDISCLOSED_DEBUG=1records the outgoing prompt and every tool call/result. To see the actual network traffic, run the brain under a proxy or watch the loopback interface; it binds127.0.0.1:5001by default (Docker uses0.0.0.0).
An agent that supports fifteen tools and frequently makes the wrong change is less useful than one that supports five and finishes predictably. Undisclosed ships a verification loop instead of a longer tool list.
The developer prompt makes these steps mandatory before a change is called done
(see <verification_workflow> in prompt.py):
- Typecheck and lint.
run_checksdetects the project's typecheck and lint commands and runs both (code_review_toolkit.py). - Review the diff.
review_diffscans the changed lines with a deterministic ruleset and returns line-anchored findings (file:line [severity] message -> suggestion). No extra model call, no external reviewer service. - Test with coverage.
run_testsruns the suite with coverage and appends acoverage_gap_plan; the agent has to state which gaps it is closing or why a gap is out of scope (test_runner_toolkit.py).
The panel keeps the receipts: a file diff, a test-run card, and the verification summary for the turn. The cards are parsed from the turn's tool activity and command output, not from the model's write-up, so the count on the test card is what the runner printed.
We judge the agent on real tasks, not benchmark theatre: do the tests pass, does the diff match the request, how often did it need intervention, and how much manual correction did the result need. Those are the numbers that decide whether a release is good. We do not publish a tool-count score.
That evaluation currently runs on this repo and on Angler. There is no public leaderboard yet. When there is one, it will report those four numbers.
The product is early, so this list is short on purpose. It grows as more work ships.
- Undisclosed itself: the editor, the agent panel, the brain, this README, and the demo video rendered inside it.
- Angler is a macOS menu-bar app that turns the flat Mac desktop into a surface with depth as you tilt the laptop lid. The app and its site were built with Undisclosed.
If you build something with it, open an issue with a link and we will add it.
| Tool | Version | Why |
|---|---|---|
| Node | 24 (use nvm use 24) |
Theia 1.76 requires Node >= 24; native modules are built for it |
| Python | 3.11 | The brain; also node-gyp needs distutils (removed in 3.12) when packaging native modules |
| uv | latest | Provisions the brain's venv from brain/pyproject.toml |
An .nvmrc pins Node 24. Copy .env.sample → .env for any model keys / tuning. Every
variable is optional - the app runs with sane defaults (see Environment variables).
From a fresh clone you need three things: Node 24, Python 3.11, and uv (see Prerequisites). You also need a workspace folder open for the agent to act on.
0. Point at a model (optional). Copy the sample env and add any keys you want. Every variable is optional and the app runs with sane defaults:
cp .env.sample .env1. Start the brain (agent backend, :5001):
./scripts/brain.sh setup # once - provisions brain/.venv via uv
./scripts/brain.sh start # start (or: restart | stop | logs | status)2. Install, build and start the editor (Theia, :3000):
nvm use 24 # Node 24 (.nvmrc); Theia 1.76 requires it
npm install # root deps (Theia + the file: packages/*)
npm run build # compile packages/* then bundle the editor (dev mode)
npm start # -> http://127.0.0.1:3000npm run build first compiles the four packages/* Theia extensions with tsc
(build:packages: undisclosed-agent, undisclosed-git, undisclosed-import,
undisclosed-languages) and then bundles the editor with Theia 1.76's esbuild
builder. Both matter: the extensions' lib/ output is gitignored, so a fresh clone has
nothing for Theia to load until you build them. Open http://127.0.0.1:3000, open a
workspace folder, add a model in the agent panel's Models UI, then talk to the agent.
The agent panel's UI bundle is committed to the repo (
packages/undisclosed-agent/assets/agent-embed/), so a normal dev build needs nothing fromagent-ui/. Rebuild it only when you are editing the agent UI (below).
Prefer one command? ./scripts/dev.sh start brings up the brain and the editor
together, and ./scripts/dev.sh rebuild does a full refresh (it pins Node 24 for you).
The editor's agent panel is agent-ui/ (React + Vite). For component work use
Storybook (hot reload, no editor rebuild):
cd agent-ui && npm install && npm run storybook # -> http://localhost:6006To rebuild the embed bundle that the editor ships, and refresh the committed copy:
npm run build:agent-ui # vite build -> packages/undisclosed-agent/assets/agent-embed/
agent-ui/uses a locally patched@stackframe/reactatagent-ui/package/@stackframe/react. That directory is gitignored, so it is not in a fresh clone andnpm installinsideagent-ui/fails until it is restored. The committed embed bundle is exactly why a normal editor dev build does not need it.scripts/release.shrunsnpm run build:agent-uias its pre-tag gate on a machine that has the patch.
Set UNDISCLOSED_DEBUG=1 (in .env) to dump each turn's prompt, tool calls, model
response and streamed chunks to the brain log and ~/.undisclosed/debug/<task>.log.
In the browser devtools console, localStorage.setItem('undisclosed_debug','1')
mirrors this on the frontend.
apps/desktop/ is the Electron Theia shell that bundles both the editor and the
brain (frozen with PyInstaller) into one installable app and auto-launches the brain on
open - no separate processes, no browser tab. Build artifacts land under
apps/desktop/dist/ (e.g. a .dmg for macOS).
The prebuilt images are unsigned (self-install). On first open on macOS:
right-click the app → Open, or xattr -cr /Applications/Undisclosed.app.
One command freezes the brain, builds the agent UI, and packages:
nvm use 20
export npm_config_python="$PWD/brain/.venv/bin/python" # node-gyp needs Python 3.11
npm run desktop:install # once, for apps/desktop node_modules
npm --prefix apps/desktop run rebuild # native modules → Electron ABI
npm run dist:mac # also dist:win / dist:linux → apps/desktop/dist/*Full details + troubleshooting: docs/PACKAGING.md.
Releases are tag-driven: CI builds installers on native runners and attaches them to a GitHub Release.
Before tagging, reproduce CI locally so a broken lockfile / plugin download / build fails here instead of burning a CI run:
./scripts/preflight-ci.sh --full # real install + rebuild + build + plugin download
./scripts/preflight-ci.sh --full --package # ...and package (electron-builder --dir)
./scripts/preflight-ci.sh # fast: workflow YAML + lockfile sync + syntax only--full runs the real install/build on THIS machine (host platform only), so it can't see
Windows/Linux-only problems. Then release:
npm run release [patch|minor|major]This gates on npm run build:agent-ui, bumps the version, commits, tags vX.Y.Z, and
pushes. The workflow (.github/workflows/release.yml) then builds on native runners
(Linux AppImage/deb, Windows nsis, and macOS for both Apple Silicon and Intel
via macos-15 + macos-15-intel) and attaches the installers to the Release. macOS
builds are unsigned: fine for self-install (right-click -> Open), but they warn
under Gatekeeper until Developer ID signing/notarization is configured. The app id /
product name / copyright used by packaging live in apps/desktop/electron-builder.yml.
agent-ui/ Agent UI (React + Vite) → built to a self-contained UMD bundle
brain/ FastAPI agent backend ("the brain"); uv + pyproject.toml;
the /api surface (providers, spaces, history, chat)
packages/
undisclosed-agent/ Theia extension: hosts the agent dock panel, the proxy,
layout & git extras; brain launcher (desktop)
undisclosed-git/ SCM fixes: staged changes open editable (save works),
plus inline diff / commit-widget styling
undisclosed-languages/ Monaco/Monarch language support + native media preview
undisclosed-import/ VS Code settings/extensions import
agent-net/ Framework-agnostic transport (fetch + SSE) shared by the
agent widget and the Electron app
apps/desktop/ Electron Theia app (packaging target; electron-builder.yml)
scripts/ brain.sh, build-brain.sh (PyInstaller), release.sh, dev.sh
docs/ PACKAGING.md, design & dev notes (assistant_message_persistence.md)
docker-compose.yml Optional containerized brain (dev convenience)
Configuration is read from .env (repo root) or brain/.env - both are loaded
by the brain at startup and gitignored. Precedence: these files use override=True, so
they win over shell exports (once a var is here it is the source of truth). See
.env.sample for the full annotated list.
Notable groups:
- Server:
UNDISCLOSED_BRAIN_PORT(default5001),UNDISCLOSED_BRAIN_HOST,UNDISCLOSED_PROXY_TARGET(override where the Theia backend sends/apicalls). - Agent behavior: per-step timeout, memory window, extended thinking (effort /
budget), tool-RAG (expose only the tools relevant to a turn) and deferred/lazy
browser + screenshot toolkits, debug mode (
UNDISCLOSED_DEBUG), storage overrides (~/.undisclosed/). - Model / LLM keys (BYOK):
OPENAI_API_KEY,GEMINI_API_KEY,GOOGLE_API_KEY. Most provider/model setup is configurable inside the app's Models UI; these vars are read directly where a toolkit needs a key without a UI-configured provider. - Design/dev integrations (each enables its corresponding agent toolkit only when
set):
FIGMA_ACCESS_TOKEN,GITHUB_ACCESS_TOKEN,MONGO_URL. The GitHub toolkit (needs thePyGithubpackage declared inbrain/pyproject.toml) covers issue create/update/close/reopen, issue and PR comments, PR create/merge/close, branch create/list, repository listing, and cross-repo issue search. The MongoDB toolkit is a read-only query surface (find/count/distinct/aggregate plus a write-guarded escape hatch) run through themongoshCLI; it redacts the connection string from every result and error, defaults to thedevdatabase (MONGO_DBto override), and never enables without a connection string. - Web search (any one enables web search): Google PSE (key+engine id), Tavily, Brave, Exa, Linkup, Bocha.
- Productivity/comms: Notion, Slack, Google Workspace (Gmail/Calendar/Drive OAuth + token paths), social (Twitter/X, LinkedIn, Reddit, WhatsApp), Lark/Feishu.
- Browser (CDP): point the brain at a running Chrome via
UNDISCLOSED_CDP_URL, or override the launched browser / command timeout.
Frontend (build-time) vars live in
agent-ui/.env, not the root.env- Vite reads only vars prefixedVITE_into the browser bundle (VITE_BRAIN_ENDPOINT,VITE_BASE_URL,VITE_PROXY_URL,VITE_APP_VERSION).
| Symptom | Likely cause / fix |
|---|---|
| Editor won't build ("Python 3.12 / distutils") | Use Node 24 + Python 3.11; nvm use 24, and point node-gyp at the venv python before packaging (export npm_config_python=...). |
| Agent panel shows a proxy/api error when adding a model | The /api proxy must target the brain (:5001), not a legacy :3001 cloud proxy. In dev run via scripts/dev.sh (sets the target) or in .env set UNDISCLOSED_PROXY_TARGET=http://127.0.0.1:5001. |
| Brain won't start | Ctrl+C a prior uv run ... may leave uv_installing.lock/uv_installed.lock in brain/ - delete them, then ./scripts/brain.sh setup. |
| Packaged app is unsigned on macOS | Right-click → Open, or xattr -cr /Applications/Undisclosed.app. See docs/PACKAGING.md for signing/notarization. |
CI hangs forever at collecting extension dependencies |
theia download:plugins talks to open-vsx over sockets with no timeout, and its "already downloaded - skipping" lines don't stop it running (or hanging) anyway. CI uses scripts/ci-download-plugins.sh: it skips the tool entirely when the pinned plugins are already present, bounds each attempt with a hard cap, and stops the tool as soon as the plugins verify. Reproduce locally with bash scripts/ci-download-plugins.sh. |
On the browser side you can run the whole stack from one terminal with
./scripts/dev.sh, which manages the brain + editor together (and defaults the proxy
target to :5001).
Apache-2.0. The full text is in LICENSE, with required attributions in NOTICE.
Portions © Eigent.ai, used under Apache-2.0. The on-device editor shell, packaging and new features © Simon Ugorji. Eclipse Theia, bundled VS Code extensions, skills and other dependencies remain under their own licenses.