Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .github/stack-bake.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@ variable "POD_BASE_IMAGE" {
default = ""
}

variable "CORE_IMAGE" {
default = ""
}

variable "VOGT_SOURCE_REF" {
default = ""
}
Expand All @@ -43,7 +39,6 @@ target "stack" {
dockerfile = "engine/Dockerfile"
args = {
POD_BASE_IMAGE = POD_BASE_IMAGE
CORE_IMAGE = CORE_IMAGE
VOGT_SOURCE_REF = VOGT_SOURCE_REF
VOGT_SOURCE_SHA = VOGT_SOURCE_SHA
VOGT_PRODUCT_VERSION = VOGT_PRODUCT_VERSION
Expand All @@ -69,7 +64,6 @@ target "gitguard" {
target = "codex-gitguard-e2e"
args = {
POD_BASE_IMAGE = POD_BASE_IMAGE
CORE_IMAGE = CORE_IMAGE
VOGT_SOURCE_REF = VOGT_SOURCE_REF
VOGT_SOURCE_SHA = VOGT_SOURCE_SHA
VOGT_PRODUCT_VERSION = VOGT_PRODUCT_VERSION
Expand Down
5 changes: 0 additions & 5 deletions .github/workbench-bake.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@ variable "POD_BASE_IMAGE" {
default = ""
}

variable "CORE_IMAGE" {
default = ""
}

variable "VOGT_SOURCE_REF" {
default = ""
}
Expand All @@ -39,7 +35,6 @@ target "stack" {
dockerfile = "engine/Dockerfile"
args = {
POD_BASE_IMAGE = POD_BASE_IMAGE
CORE_IMAGE = CORE_IMAGE
VOGT_SOURCE_REF = VOGT_SOURCE_REF
VOGT_SOURCE_SHA = VOGT_SOURCE_SHA
VOGT_PRODUCT_VERSION = VOGT_PRODUCT_VERSION
Expand Down
29 changes: 10 additions & 19 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ permissions:
contents: read

env:
# The core alone. Built and pushed per commit because the merged image
# below copies `/opt/vogt` out of it by digest — a build input, not
# a deployment target (DEPLOYMENT.md).
# The core alone. Built and pushed per commit until I7 retires the root
# Dockerfile. The merged image no longer takes its core from here (WI-1078),
# so nothing downstream reads it; it is not a deployment target (DEPLOYMENT.md).
IMAGE: ghcr.io/thedancingdeveloper-org/vogt
# The product — the Python core *and* the session engine in one container,
# which is what `deploy/stack.compose.yml` runs and the one supported
Expand Down Expand Up @@ -133,10 +133,6 @@ jobs:
contents: read
packages: write
id-token: write
# The merged image derives its core from this one by digest, so the
# digest has to leave this job rather than being re-derived from a tag.
outputs:
digest: ${{ steps.push.outputs.digest }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1

Expand Down Expand Up @@ -271,9 +267,9 @@ jobs:
{
echo "### Built the core from \`${GITHUB_SHA:0:7}\`"
echo
echo "A build input: the merged image job lifts its core from this"
echo "digest. This is not the image to pin — pin the *merged* stack"
echo "digest reported below."
echo "A standalone core image. The stack image no longer takes its"
echo "core from this one, so this is not the image to pin: pin the"
echo "*merged* stack digest reported below."
echo
echo '```'
echo "${IMAGE}@${DIGEST}"
Expand Down Expand Up @@ -615,10 +611,9 @@ jobs:

stack-image:
name: build and publish the merged image
# `needs: image` is not ordering for its own sake: the merged image copies
# /opt/vogt out of the core image this run just published, by digest.
# `needs: pod-base` supplies the toolchain base it now builds `FROM`.
needs: [base-images, pod-base, image]
# `needs: pod-base` supplies the toolchain base it now builds `FROM`. The
# core-image job is not a dependency: the stack builds its own vogt-core.
needs: [base-images, pod-base]
runs-on: [self-hosted, publish]
timeout-minutes: 60
permissions:
Expand Down Expand Up @@ -691,7 +686,6 @@ jobs:
id: push
env:
POD_BASE_IMAGE: ${{ needs.pod-base.outputs.image }}
CORE_IMAGE: ${{ env.IMAGE }}@${{ needs.image.outputs.digest }}
VOGT_SOURCE_REF: ${{ github.ref_name }}
VOGT_SOURCE_SHA: ${{ github.sha }}
# metadata-action's `labels` output is newline-separated key=value
Expand All @@ -712,7 +706,6 @@ jobs:
# buildx refuses a local write without an explicit entitlement.
--allow "fs.write=${E2E_DIR}"
--set "*.args.POD_BASE_IMAGE=${POD_BASE_IMAGE}"
--set "*.args.CORE_IMAGE=${CORE_IMAGE}"
--set "*.args.VOGT_SOURCE_REF=${VOGT_SOURCE_REF}"
--set "*.args.VOGT_SOURCE_SHA=${VOGT_SOURCE_SHA}"
--set "*.args.VOGT_PRODUCT_VERSION=0.7.17"
Expand Down Expand Up @@ -839,7 +832,7 @@ jobs:

stack-workbench-image:
name: build and publish the workbench stack image
needs: [base-images, pod-base-workbench, image]
needs: [base-images, pod-base-workbench]
runs-on: [self-hosted, publish]
timeout-minutes: 60
permissions:
Expand Down Expand Up @@ -884,7 +877,6 @@ jobs:
id: push
env:
POD_BASE_IMAGE: ${{ needs.pod-base-workbench.outputs.image }}
CORE_IMAGE: ${{ env.IMAGE }}@${{ needs.image.outputs.digest }}
VOGT_SOURCE_REF: ${{ github.ref_name }}
VOGT_SOURCE_SHA: ${{ github.sha }}
LABELS: ${{ steps.meta.outputs.labels }}
Expand All @@ -896,7 +888,6 @@ jobs:
--file .github/workbench-bake.hcl
--metadata-file "$META_FILE"
--set "*.args.POD_BASE_IMAGE=${POD_BASE_IMAGE}"
--set "*.args.CORE_IMAGE=${CORE_IMAGE}"
--set "*.args.VOGT_SOURCE_REF=${VOGT_SOURCE_REF}"
--set "*.args.VOGT_SOURCE_SHA=${VOGT_SOURCE_SHA}"
--set "*.args.VOGT_PRODUCT_VERSION=0.7.17"
Expand Down
11 changes: 5 additions & 6 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,9 @@ jobs:
ENGINE_BIND: "127.0.0.1"
ENGINE_PORT: "8910"
ENGINE_PUBLIC_URL: "http://127.0.0.1:8910"
# Build the core image locally and point BOTH the core service's `image:`
# and the engine build's `CORE_IMAGE` arg (both read `${VOGT_IMAGE}`) at
# it, so nothing pulls the private GHCR core — the job runs with no
# registry credentials.
# Build the core image locally and point the core service's `image:`
# (it reads `${VOGT_IMAGE}`) at it, so nothing pulls the private GHCR
# core — the job runs with no registry credentials.
VOGT_IMAGE: "vogt-core:local"
steps:
# A clean clone, every run: the whole point is that nothing on the runner
Expand Down Expand Up @@ -113,11 +112,11 @@ jobs:
set -euo pipefail
# The core is private on GHCR and the runner has no registry
# creds, so build everything from the repo's own Dockerfiles. VOGT_IMAGE
# points the core service and the engine build-arg at the local core.
# points the core service at the local core.
# The e2e engine bakes in the fake-agent + preset, so nothing is
# host-bind-mounted (the runner's FS is not the docker daemon's).
docker build -t vogt-core:local -f Dockerfile .
docker build -t vogt-engine:local -f engine/Dockerfile --build-arg CORE_IMAGE=vogt-core:local .
docker build -t vogt-engine:local -f engine/Dockerfile .
docker build -t vogt-engine-e2e:local -f deploy/e2e.engine.Dockerfile .
# The live-Playwright image: web/ + browsers baked in so the
# `live` project runs in-network with no bind-mount. Public base image,
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/rust-core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ jobs:
run: |
set -euo pipefail
docker build -t vogt-core:rust -f deploy/rust-core.Dockerfile .
docker build -t vogt-engine:local -f engine/Dockerfile --build-arg CORE_IMAGE=vogt-core:rust .
docker build -t vogt-engine:local -f engine/Dockerfile .
docker build -t vogt-engine-e2e:local -f deploy/e2e.engine.Dockerfile .
docker build -t vogt-e2e-playwright:local -f deploy/e2e.playwright.Dockerfile .
# Tokens go through the named volume (VOGT_CORE_TOKEN_VALUE,
Expand Down
5 changes: 0 additions & 5 deletions deploy/engine.overlay.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,6 @@ services:
context: ..
dockerfile: engine/Dockerfile
args:
# The published core, lifted whole into the engine image so one
# container carries both halves. Defaults to the same public image the
# base would run, so the embedded core matches it; pin a digest for a
# real deployment. The Dockerfile already carries a public default too.
CORE_IMAGE: "${VOGT_IMAGE:-ghcr.io/thedancingdeveloper-org/vogt:0.7.17}"
# Optional image-managed agent CLIs. When enabled, versions come from
# engine/agent-versions.env unless an operator deliberately overrides
# one with a reproducible build arg.
Expand Down
10 changes: 3 additions & 7 deletions deploy/rust-core.overlay.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
# docker compose -f deploy/vogt.compose.yml -f deploy/engine.overlay.yml \
# -f deploy/rust-core.overlay.yml up -d
#
# The `vogt` service is the core the engine fronts. The engine image also
# carries its own core (CORE_IMAGE), and a stack whose two halves are
# different binaries is not the thing under test. The build arg makes the
# engine image lift the same Rust image.
# The `vogt` service is the core the engine fronts. The engine image builds
# its own vogt-core from the same source, so no build arg is needed here; the
# core it fronts is still the `vogt` service above.
#
# VOGT_ENGINE_URL is what makes session.* on the core a call to the real
# engine rather than an empty list with "no session engine is configured".
Expand Down Expand Up @@ -64,9 +63,6 @@ services:
- smoke-work:/home/sprooty/Working

engine:
build:
args:
CORE_IMAGE: ${VOGT_IMAGE:-vogt-core:rust}
depends_on:
token-init:
condition: service_completed_successfully
Expand Down
5 changes: 3 additions & 2 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,9 @@ VOGT_IMAGE=vogt-core:local docker compose \
-f deploy/vogt.compose.yml -f deploy/engine.overlay.yml up --build -d
```

`VOGT_IMAGE` names both the core the base runs and the `CORE_IMAGE` the engine
build lifts into its own image, so one variable points both at your build. The
`VOGT_IMAGE` names the core the base runs. The engine image builds its own
`vogt-core` from the same source rather than lifting one, so this variable is
the only thing to point at your build. The
engine is then the front door at `http://127.0.0.1:8910`: the PWA at `/`,
`/api/vogt/*` proxied to the core, `/mcp` for agents, `ENGINE_TOKEN` as the
bearer. Both containers read the core token from `deploy/vogt-core-token`, so
Expand Down
27 changes: 16 additions & 11 deletions docs/CUSTOMISATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -365,17 +365,22 @@ whatever its image declares — frequently an empty anonymous volume that
Docker creates fresh on each deploy.

So **administrative commands go to the core**: `project import`,
`project register`, `token issue`, `status`. Anything you would run to
change what the instance knows belongs where the instance's state is. The
tempting `vogt init` that the error message suggests is exactly the wrong
response — it would initialise a second, empty instance in the container
that should not have one.

One wrinkle if you go looking: the core's CLI may be inside a virtualenv
(`/opt/vogt/.venv/bin`) that is on `PATH` for a plain `docker exec` but not
for a *login* shell. `docker exec core vogt status` finds it;
`docker exec core sh -lc 'vogt status'` may not, and reports
`vogt: not found` — a missing binary that is not missing.
`project register`, `token issue`, `user create`, `status`, `backup`,
`restore`. In the stack image the core is the Rust `vogt-core`, and its `vogt`
carries all of these (see [DEPLOYMENT.md](DEPLOYMENT.md), "The stack image's
CLI"); only `diagnostics`, `engine status` and `connect` are not yet ported.
Anything you would run to change what the instance knows belongs where the
instance's state is. The tempting `vogt init` that the error message suggests
is exactly the wrong response — it would initialise a second, empty instance
in the container that should not have one.

One wrinkle if you go looking, in the Python core image (the root
`Dockerfile`, published until the swap): its CLI is inside a virtualenv
(`/opt/vogt/.venv/bin`) that is on `PATH` for a plain `docker exec` but not for
a *login* shell, so `docker exec core sh -lc 'vogt status'` may report
`vogt: not found` — a missing binary that is not missing. The stack image has
no virtualenv; its `vogt` is the Rust binary, linked into `/usr/local/bin`,
and is found either way.

## Layer 3 — extending the image

Expand Down
20 changes: 17 additions & 3 deletions docs/DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ sidecar, released as a pair and run by one Compose file,
├── /mcp proxied to the core
├── /api/auth/login, /api/install/... proxied untouched; the core self-gates
└── /healthz, /readyz
vogt serve (:8000, loopback inside the container, never published)
vogt-core serve (:8000, loopback inside the container, never published)
├── /api/... REST (OpenAPI at /openapi.json)
├── /mcp MCP streamable HTTP transport
└── /health/live, /health/ready, /version
Expand Down Expand Up @@ -526,13 +526,27 @@ instance id), otherwise every difference is a conflict.
fresh prod backup: the clone baseline is now behind both sides, so a
second carry would report every item touched by the first as a conflict.

**Migrations run at boot.** The entrypoint runs `vogt init` before every
`vogt serve`. `init` is idempotent: it creates the instance on a new volume,
**Migrations run at boot.** The entrypoint runs `vogt-core init` before every
`vogt-core serve`. `init` is idempotent: it creates the instance on a new volume,
brings an existing one forward to this build's schema, and leaves the audit
history alone after that. Until the schemas match, `/health/ready` answers
503 naming the store and both schema numbers. Migrations are forward-only and
run under a lock. Read [`SCHEMA.md`](SCHEMA.md) before a major version.

**The stack image's CLI is the Rust core.** On the stack image's PATH, `vogt`,
`vogt-core` and `vogt-mcp-remote` are one binary, `vogt-core`, built from
`engine/core` in the same build (WI-1078). Besides `init` (with `--check`) and
`serve`, `vogt` carries the registry's CLI: the admin verbs `status`,
`token issue`, `user create`, `backup`, `restore`, `migrate`, `project import`
and `project register`, and the rest of the commands the Python CLI had
(`work`, `session`, `forge`, `export`, `import`, `clone`, ...), with the same
global `--json` and `--data-dir`. Run them in the container that holds the
store, for example `docker exec <stack> vogt token issue ...`. Three commands
are bound but not yet ported, and answer with a "not available" error rather
than an empty success: `diagnostics`, `engine status` and `connect`. Use the
API and MCP endpoints for those. `vogt-mcp` is the engine's own stdio script,
not the `mcp` command.

**Upgrade.**

1. Take a backup.
Expand Down
17 changes: 7 additions & 10 deletions docs/ENGINE.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,16 +102,13 @@ the git routes.
The merged image is built from the **repository root**, not from `engine/`:

```bash
docker build -f engine/Dockerfile \
--build-arg CORE_IMAGE=ghcr.io/thedancingdeveloper-org/vogt:latest \
-t vogt-engine .
docker build -f engine/Dockerfile -t vogt-engine .
```

The Dockerfile's stages are the PWA bundle, the Rust binary with that bundle
embedded, the published core image (lifted whole, so the merged image runs
*the* public core rather than a second build of it — `CORE_IMAGE` defaults to
the public `:latest` tag as a placeholder for a local build, and CI overrides
it with a digest), and the runtime. The runtime stage starts `FROM` a
embedded, and `vogt-core` built beside it from the same source (there is no
Python core in the image and no `CORE_IMAGE` argument), and the runtime. The
runtime stage starts `FROM` a
**dev-pod base** (`engine/Dockerfile.pod`, published as
`ghcr.io/thedancingdeveloper-org/vogt-pod-base:lean-*`): an Ubuntu image
carrying the toolchains an agent working *inside* a session is likely to want
Expand All @@ -124,9 +121,9 @@ built on it as `vogt-stack-workbench`; nothing in this repository deploys
that image. It is a
development pod rather than a hardened service image — it runs as a named
user with `sudo`, with a writable home — which is why the core image at the
repository root is a separate build rather than a stage of this one: it is
the hardened input this image lifts its core from, not something to deploy on
its own (`DEPLOYMENT.md`).
repository root is a separate build rather than a stage of this one: it is the
hardened core-only image, not something to deploy on its own (`DEPLOYMENT.md`).
The merged image no longer takes its core from it; that image is retired at I7.

Build arguments worth knowing: `INSTALL_AI_CLIENTS=true` bakes in the `claude`
and `opencode` CLIs at the Renovate-pinned versions in
Expand Down
4 changes: 2 additions & 2 deletions engine/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ Things about the build that are easy to get wrong:
`mkdir -p web/dist && touch web/dist/.placeholder` only as a guard. A
production build must embed a real bundle.
- `engine/Dockerfile` takes the repository root as its build context and
requires `CORE_IMAGE` (the published core image, by digest in CI) — it lifts
the core out of that image rather than building it a second time.
builds `vogt-core` in `server-build`, from the same source as the engine. It
takes no `CORE_IMAGE`; the root `Dockerfile` core image is retired at I7.
- The runtime stage starts from `engine/Dockerfile.pod`, the dev-pod toolchain
base, published separately and passed in by digest as `POD_BASE_IMAGE`.
Toolchain changes go there; per-commit things (the agent CLIs) stay in
Expand Down
Loading
Loading