Skip to content

Latest commit

 

History

History
439 lines (356 loc) · 18.3 KB

File metadata and controls

439 lines (356 loc) · 18.3 KB

Getting started with Vogt

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.

Choose an installation path

There are two:

  • The published stack (recommended) — pulled from the registry, nothing to build. It is one product stack, not one container: the vogt-stack image (a pod carrying the Python core, the Rust session engine that serves the Solid PWA at / on one port, 8910, and the claude and codex CLIs) plus the vogt-voice sidecar, 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.

Docker Compose (recommended)

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-token

Edit 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 down

Three 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.

Local Python

Prerequisites:

  • Python 3.11 or newer; and
  • uv.

Install the development environment and initialise a local instance:

uv sync
uv run vogt init

The default data directory is ~/.local/share/vogt (or $XDG_DATA_HOME/vogt). To choose another location:

VOGT_DATA_DIR=/srv/vogt uv run vogt init

The 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 backlog

Collection is scoped to registered projects. Vogt does not crawl arbitrary filesystem roots or discover projects on its own.

Run the HTTP server locally

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 $login

Either 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.

First project and first work item

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 backlog

The 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:rw
docker 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.

Optional GitHub integration

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-token

The 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.

Optional MCP integration

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-mcp

For 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 session engine

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.

Backup, upgrade, and removal

Use the lifecycle commands before upgrading a local or container deployment:

uv run vogt backup --reason "backup before upgrade"
uv run vogt migrate

For 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 --volumes

This removes the stack's named volumes and cannot be undone by Docker.

Where to go next