This guide gets a new operator from nothing to a working Vogt — the browser
front end, the API, terminals, and the agent CLIs — with Docker as the only
prerequisite. A forge token, an AI provider, and an MCP client are all
optional and documented separately. Production concerns — a reverse proxy,
TLS, digest pinning, backups on a schedule — are in
docs/DEPLOYMENT.md; this guide stops at a working
instance.
There are two:
- The published stack (recommended) — pulled from the registry, nothing to
build. It is one product stack, not one container: the
vogt-stackimage (a pod carrying the Python core, the Rust session engine that serves the Solid PWA at/on one port, 8910, and theclaudeandcodexCLIs) plus thevogt-voicesidecar, a second container that gives speech out of the box. The two images are released as a pair and run by one Compose file,deploy/stack.compose.yml. Voice is on by default and can be turned off or repointed at another provider (Voice). This is Vogt as it is meant to be run. - Local Python — the core alone as a plain package, for development or a
single-user workstation over the CLI, REST and MCP. No browser front end.
It writes to the normal Vogt data directory unless you set
VOGT_DATA_DIR.
Prerequisites:
- Docker Engine 24 or newer with the Compose plugin;
- Git, to fetch the two deploy files (or copy them by hand); and
- a host port that is available for the web UI/API.
From the repository root:
git clone https://github.com/TheDancingDeveloper-org/vogt.git
cd vogt
cp deploy/stack.env.example deploy/.env
openssl rand -hex 32 > deploy/vogt-core-tokenEdit deploy/.env before starting if you need to: change ENGINE_PORT if
8910 is already in use. Nothing in it is a human credential — you will choose
a password in the browser — and ENGINE_TOKEN, an optional break-glass token
for the engine, can stay empty. The file you just created is the stack
secret, read by both halves inside the container, which is what lets the
two talk to each other on the first boot without a second deploy.
Start it. There is no --build: the stack image already carries the
core, the engine and the PWA, and the voice sidecar is pulled beside it.
docker compose -f deploy/stack.compose.yml up -d --wait--wait blocks until the healthcheck reports healthy. Without it, a curl
right after up -d can race the container: the core's vogt init runs
first, then the engine comes up, and the healthcheck's start_period is
60s, so an immediate probe can see connection-refused rather than a real
answer.
The port publishes to 127.0.0.1 unless you set ENGINE_BIND. The example
will not put a pod carrying sudo and agent CLIs on a network interface
because nobody said to.
Check it is up:
curl http://localhost:8910/healthz # the engine answers
curl http://localhost:8910/readyz # ...and reports the core behind it/readyz names each check — vogt_core, workspace_agreement,
backup_agreement — with a pass or fail and a reason. It deliberately stays
ready when the core is absent, because restarting the container would not
revive a core and would kill every live terminal; read the body, not just
the status.
Open http://localhost:8910/ in a browser. The engine serves the PWA — the
board, backlog, terminals, agent tasks, and the voice assistant — at the
root. A fresh instance greets you with the first-run wizard: give your name,
a username (suggested from the name) and a password, and it creates your
admin login and signs you in. The stack secret you wrote above does not
count as an operator — it is the two halves' credential for each other,
bound to an agent actor — so the wizard stays open until the first person
has a login (see First run below for the
exact rule). docs/USER_GUIDE.md is the tour.
If you see only "Sign in". The wizard is shown while curl http://localhost:8910/api/install/status answers {"install_mode": true}.
It answers false once somebody already has a login, when the deployment
set VOGT_INSTALL_BOOTSTRAP_ENABLED=false, and — on releases up to and
including v0.7.7 — as soon as the stack secret was adopted, which is the
case #903 fixed. An instance that ran v0.7.7 or earlier stays closed after
upgrading (the upgrade latches any store that already held a token, so a
running instance is never reopened to an unauthenticated bootstrap). Either way, create the first operator from inside the
container instead; it prompts for the password (--password-file PATH and
--password-stdin are the non-interactive forms), and you then sign in
with that username and password:
docker compose -f deploy/stack.compose.yml exec vogt \
vogt user create --username <name> --scopes admin --reason "Create first operator"Stop or inspect the instance with:
docker compose -f deploy/stack.compose.yml ps
docker compose -f deploy/stack.compose.yml logs -f vogt
docker compose -f deploy/stack.compose.yml downThree named volumes survive down: vogt-data (the core's databases and
backups), engine-home (the pod's home — agent state, session scratch, the
Working tree sessions run in) and engine-agent-clis (agent CLI versions
pinned at runtime; the pod re-downloads them if it is absent). Do not add
--volumes unless you intentionally want to remove the instance and
everything it holds.
The base file is never edited; every deployment states only its difference
from it as an overlay or an environment value. That is the whole
customisation model, and docs/CUSTOMISATION.md is the
long form.
Prerequisites:
- Python 3.11 or newer; and
uv.
Install the development environment and initialise a local instance:
uv sync
uv run vogt initThe default data directory is ~/.local/share/vogt (or
$XDG_DATA_HOME/vogt). To choose another location:
VOGT_DATA_DIR=/srv/vogt uv run vogt initThe CLI uses the same registry and application layer as the HTTP server. Run
commands from the checkout or set VOGT_DATA_DIR explicitly:
uv run vogt status
uv run vogt project register \
--name example \
--root-path "$PWD" \
--reason "register the checkout for observation"
uv run vogt sweep --reason "collect the initial repository evidence"
uv run vogt backlogCollection is scoped to registered projects. Vogt does not crawl arbitrary filesystem roots or discover projects on its own.
The server requires an explicit listen address and port. This prevents a local default from accidentally becoming a network exposure:
VOGT_PUBLIC_URL=http://127.0.0.1:8000 \
uv run vogt serve --host 127.0.0.1 --port 8000 --no-auth--no-auth is suitable only for a loopback listener. For a network listener,
start with authentication enabled (the default), initialise the instance,
and issue a scoped token from a trusted local process.
First run (install mode). A freshly initialised instance has no
operator, and while no person holds a credential — no token bound to a
non-agent actor, revoked or not, and no password login — the server is in
install mode: GET /api/install/status answers {"install_mode": true} and an unauthenticated
POST /api/install/bootstrap names the first operator. Given a password
(at least 8 characters) it creates that person's login with the admin
scope and returns a session — an expiring core token — which is what the
browser first-run wizard rides; username is derived from the display name
(ada-lovelace) when omitted, and a username without a password is refused
with 400. Without a password it is the headless bootstrap for scripted
installs and returns an admin API token instead:
curl -s http://127.0.0.1:8000/api/install/bootstrap \
-H 'Content-Type: application/json' \
-d '{"display_name": "Ada Lovelace"}'
# or, to come out of it with a login rather than a long-lived token:
curl -s http://127.0.0.1:8000/api/install/bootstrap \
-H 'Content-Type: application/json' \
-d '{"display_name": "Ada Lovelace", "username": "ada", "password": "correct horse battery"}'On Windows PowerShell, curl is an alias for Invoke-WebRequest and the
quoting above does not survive. Send a plain JSON string instead — and pass
the password as a literal string, not the output of Get-Content, which
carries file metadata that turns the body into something the core refuses
with 422:
$body = @{ display_name = "Ada Lovelace"; username = "ada"; password = "correct horse battery" } | ConvertTo-Json
Invoke-RestMethod -Method Post -Uri http://127.0.0.1:8000/api/install/bootstrap `
-ContentType 'application/json' -Body $body
# the sign-in call has the same shape:
$login = @{ username = "ada"; password = "correct horse battery" } | ConvertTo-Json
Invoke-RestMethod -Method Post -Uri http://127.0.0.1:8000/api/auth/login `
-ContentType 'application/json' -Body $loginEither answer carries the secret exactly once and a token bound to the actor
it just created (human:ada-lovelace), and the write is audited to that
actor. The moment a person holds a credential — this one, a token issued to
a person any other way, or a login made with vogt user create — install
mode closes itself and the bootstrap refuses with install_closed. Tokens
bound to agent actors never close it: the stack secret adopted at init
(bootstrap_core_token_file), the brokered agent token and session tokens
authenticate as they always did, but none of them is an operator. Once
closed it stays closed: the store latches it, so neither revoking every
token nor removing every user reopens it, and a lockout is fixed from a
trusted local process, below. An instance upgraded from v0.7.7 or earlier
that already held any token — the stack secret included — was latched
closed by that upgrade, exactly as it was before; if nobody can sign in to
it, create the operator with vogt user create. The self-closing door is safe because the port publishes on
loopback by default (VOGT_BIND_IP falls back to 127.0.0.1); publish it
to a real interface only after the first operator exists, or set
install_bootstrap_enabled = false and create the operator with vogt user create instead.
Local (uv run). The token is bound to the OS user running the command:
uv run vogt token issue \
--actor local:$(id -un) \
--name browser \
--scopes read,work.write,project.write \
--reason "create a browser credential"Docker Compose. The container runs as a fixed identity, sprooty, not
yours, so local:$(id -un) names an actor that was never created and the
command fails with no actor with identity '...' — create it with 'actor create' first. vogt init bootstraps the actor local:sprooty inside the
container, so issue the token as that actor, from the container that owns
the data directory:
docker compose -f deploy/stack.compose.yml exec vogt \
vogt token issue \
--actor local:sprooty \
--name claude-code \
--scopes read,work.write,project.write \
--reason "create an agent credential"This is the token for a CLI, an MCP client, or a script talking to the core
directly — an agent's credential. The secret is shown once. Store it in a
file with restrictive permissions and send it as Authorization: Bearer ...;
never put it in a URL or command-line argument.
People sign in with a password. The browser does not hold an API token: it holds the session a username-and-password login mints, which the engine accepts on its own routes by asking the core who it is and forwards to the core on every Vogt call. The first operator's login comes from the wizard (or the bootstrap above); every other one is created by an admin, with the password read from a file, from stdin, or from a hidden prompt on a terminal — never from the command line:
uv run vogt user create --username ada --display-name "Ada Lovelace" \
--password-file ~/.vogt-password --reason "give Ada a login"A login's scopes default to read,work.write,project.write; --scopes
changes them, and --actor attaches the login to an existing human actor
instead of creating human:ada. vogt user passwd replaces a password (and
revokes the person's sessions unless told not to), vogt user remove takes
a login away and leaves the actor and its history, vogt auth whoami says
who any credential is, and vogt auth logout revokes the one a call arrived
with.
Register the repository you want Vogt to observe, then sweep it:
uv run vogt project register \
--name my-project \
--root-path /path/to/my-project \
--reason "start tracking this repository"
uv run vogt sweep --reason "collect repository state"
uv run vogt project get --slug my-project
uv run vogt backlogThe default collectors read local Git state, configured source markers, and dependency references. They return findings; the sweep records observations and coverage. A collector cannot silently change declared work.
Docker Compose. deploy/stack.compose.yml mounts nothing from the host
by default, so --root-path above has nothing to observe until you bind-mount
a real checkout into the container. Mount it under the pod's Working tree —
that is both the core's import root and the root sessions open in, so a
project registered there is one a terminal can also be opened for. The pod
runs as uid 1000, so the host directory must be readable (and, for sessions,
writable) by that uid. See docs/CUSTOMISATION.md, "Observing repositories
on a host path" for
the full pattern. A minimal example:
# my-overlay.yml
services:
vogt:
volumes:
- /srv/my-project:/home/sprooty/Working/my-project:rwdocker compose -f deploy/stack.compose.yml -f my-overlay.yml up -d --wait
docker compose -f deploy/stack.compose.yml exec vogt \
vogt project register \
--name my-project \
--root-path /home/sprooty/Working/my-project \
--reason "start tracking this repository"
docker compose -f deploy/stack.compose.yml exec vogt \
vogt sweep --reason "collect repository state"To create a new contract-shaped project instead:
uv run vogt project create \
--name new-project \
--root-path /path/to/new-project \
--reason "start a new tracked project"Every mutating operation requires a reason and is written to the audit log.
GitHub is an optional read/write-back module. The core remains complete when no token is configured. To enable collection, place a fine-scoped GitHub token in a file readable by the Vogt process and set:
export VOGT_GITHUB_TOKEN_FILE=/secure/path/github-tokenThe absence of this file means “GitHub was not collected”, not “there are no
GitHub subjects”. See docs/CONFIG.md and the GitHub section of
the user guide before enabling write-back.
MCP is not required for startup or normal use. When you want to connect an agent, use the built-in Vogt adapter after the server is running:
VOGT_DATA_DIR=/path/to/vogt vogt-mcpFor a remote instance, configure VOGT_URL and VOGT_TOKEN_FILE for
vogt-mcp-remote. Other MCP servers you may run alongside Vogt in an agent
client — another MCP server, a language server — are your agent's
configuration, not Vogt's; the published image installs, contacts, and
requires none of them.
The Rust session engine and its PWA (engine/, web/) are what give you
terminal sessions, file and git APIs, agent tasks, push notifications, and a
voice/chat assistant. They are part of the product, not an add-on: the stack
image carries them already wired to the core, and nothing here needs
configuring. Only the Local Python path runs without them, and there the
core simply reports that no engine is configured.
docs/ENGINE.md covers the engine's own settings;
the assistant provider is any OpenAI-compatible chat endpoint, configured
with ENGINE_ASSISTANT_BASE_URL, ENGINE_ASSISTANT_API_KEY, and
ENGINE_ASSISTANT_MODEL.
Use the lifecycle commands before upgrading a local or container deployment:
uv run vogt backup --reason "backup before upgrade"
uv run vogt migrateFor Compose, take the backup from inside the running container:
docker compose -f deploy/stack.compose.yml exec vogt \
vogt backup --reason "backup before upgrade"Upgrade by changing VOGT_STACK_IMAGE and VOGT_VOICE_IMAGE in
deploy/.env to the new release's digests (or tags) — they are one release
pair — then up -d --wait with the same Compose file; startup applies
forward-only migrations. Keep the old images and backup until the new
readiness check and a restore test succeed.
To remove the example completely, first make a backup, then run:
docker compose -f deploy/stack.compose.yml down --volumesThis removes the stack's named volumes and cannot be undone by Docker.
docs/USER_GUIDE.mdexplains the PWA, ranked views, projects, drift, audit, and all supported CLI/API surfaces.docs/AGENT_GUIDE.mdis for an agent running product work through Vogt — connecting over MCP/REST/CLI, picking up ranked work, linking branches and PRs back to items, and a drop-in block for your own repository.docs/CONFIG.mdis generated from the configuration schema.docs/CUSTOMISATION.mdnames the supported extension points — configuration, Compose overlays, image extension, your own front door — for a deployment that needs more than the base.docs/DEPLOYMENT.mdis the production guide — exposure, reverse proxy and TLS, digest pinning, backups, and upgrades.docs/ENGINE.mdis the session engine's reference.docs/ARCHITECTURE.mdstates the architecture and the product boundary: what the stack is, and which integrations are optional.- The public demo sites show the product running: https://vogt-demo.thedancingdeveloper.com/ and, for the mobile shell, https://vogt-mobile-demo.thedancingdeveloper.com/.