vise runs coding-agent sessions against your repositories on hosts you control. You describe a task, the server schedules it onto an enrolled host, the host runs an agent harness (such as Claude Code) in an isolated workspace using the Agent Client Protocol, and the result comes back as a pull request, a pushed branch, or a live event stream you can tail from the CLI. The API is documented with OpenAPI and the Rust client is generated from that spec, so the CLI, the host and any integration you write all speak the same contract.
One line stands up the whole stack on a macOS (Apple Silicon) or Linux (x86_64, arm64) machine that has Docker and git:
curl -fsSL https://vise.sh/install | sh
vise.sh/install serves scripts/install.sh from this
repository. Without sudo, it:
docker (with the compose plugin) and git, and warns if
claude (Claude Code) is missing: the claude-code harness needs it, the
echo harness does not;VISE_GITHUB_PAT) and
writes ~/.vise/.env plus a ~/.vise/docker-compose.yml that runs Postgres
and ghcr.io/vise-sh/vise-server;docker compose up -d and waits for the API on http://localhost:3000;vise-cli and vise-host archives from the latest
GitHub release, checks them
against the SHA256SUMS.txt attached to that release, and puts them in
~/.vise/bin (add it to your PATH);vise-host in the background.It is safe to re-run: an existing ~/.vise/.env is kept unless you say
otherwise, and the binaries and server image are upgraded in place. Set
VISE_VERSION=v0.2.0 to pin a release, VISE_PORT to move the API, or
VISE_HOME to install somewhere other than ~/.vise; the header of the script
lists every override. Then:
vise sessions create "add a --json flag to the ls command" --repo owner/repo --watch
The host on this machine is a plain background process, managed with
vise host:
vise host status # running / not running (exit 1 when stopped)
vise host logs -f # tail ~/.vise/logs/host.log
vise host stop # SIGTERM via ~/.vise/host.pid
vise host start # reads VISE_HOST_TOKEN and VISE_URL from ~/.vise/.env
vise host start -- --keep-workspaces passes extra flags through to
vise-host. Server logs are at docker compose -f ~/.vise/docker-compose.yml logs -f.
vise host stop
docker compose -f ~/.vise/docker-compose.yml down -v # containers and the Postgres volume
rm -rf ~/.vise
and remove the ~/.vise/bin line from your shell profile.
Every release ships the server as a container image,
ghcr.io/vise-sh/vise-server (linux/amd64 and linux/arm64, tagged with the
version and latest), plus vise-cli and vise-host archives for macOS
(arm64) and Linux (x86_64, arm64) on the
releases page. With those you
need Docker and nothing else; the server applies its own database migrations
on startup.
git clone https://github.com/vise-sh/vise && cd vise
cp .env.example .env # set VISE_GITHUB_PAT (or the App settings, see below)
docker compose up -d # Postgres 17 + vise-server on http://localhost:3000
Swagger UI is at /docs. The image is public, so no docker login is needed
to pull it. With a GitHub App instead of a PAT, add the overlay that mounts the
private key:
docker compose -f docker-compose.yml -f docker-compose.github-app.yml up -d.
Release images are signed with cosign; SECURITY.md
shows how to verify a pulled image.
To run the server from source instead, you also need Rust (stable) and just:
just db-up # Postgres only
cargo run -p vise-server # applies migrations, then serves on :3000
Enroll a host and start it with the token that is printed (use the release
binaries or cargo run -p ... interchangeably):
vise-cli hosts create laptop
VISE_HOST_TOKEN=<token> vise-host
(cargo run -p vise-cli -- host start --bin target/debug/vise-host runs the
same thing in the background, with the token taken from VISE_HOST_TOKEN or
~/.vise/.env.)
Then create a session and watch it run:
vise-cli sessions create "add a --json flag to the ls command" \
--repo your-org/your-repo --watch
Use --harness echo to try the flow without a real agent, and
sessions ls / sessions events <id> to inspect what happened.
A session that ends by opening a pull request does not stop being useful
there. The server keeps polling the PR (every 60 seconds by default, see
VISE_PR_POLL_INTERVAL_SECS) until it merges or closes, and records a derived
snapshot on the session:
pr_status.state: review_pending, changes_requested, approved,
merged, closed, or sync_error when the PR became unreadable. The state
is reduced from GitHub's review list (latest review per reviewer wins, an
outstanding request for changes beats approvals, approvals on an older
commit are stale); raw review comments are never stored.pr_status.checks: pending, passing or failing, from the check runs
on the head commit.Every transition is appended to the session's event stream as a
pr_state_changed or checks_state_changed event, so sessions watch <id>
on a finished session tails the PR until it merges or closes, and
sessions ls / sessions get show the current state.
When a reviewer asks for changes, spawn a follow-up:
cargo run -p vise-cli -- sessions follow-up <session-id> \
--instructions "keep the public API stable" --watch
The follow-up inherits the parent's agent configuration, is checked out on the
PR's head branch so its pushes update the same PR, and receives the current
review threads (with file and line context) and failing check names in its
input, composed server-side at creation time. Its outcome is pr_updated;
tracking stays with the session that opened the PR, however many follow-ups
chain off it.
The tracker and the follow-up endpoint reuse the server's GitHub credential (App or PAT, see below), which needs to read pull requests and check runs in addition to the Contents: read/write that hosts need to push: Pull requests: read and Checks: read for a GitHub App, Pull requests: read and Actions: read for a fine-grained PAT.
github_repo sessions need the server to authenticate to GitHub, both to hand
hosts a token for cloning and pushing and to read pull requests for tracking
and follow-ups. Two options are supported:
VISE_GITHUB_APP_ID
and VISE_GITHUB_APP_PRIVATE_KEY_PATH. The server mints a short-lived
installation token scoped to the session's repository for every session.
The App needs Contents: read/write, Pull requests: read/write and
Checks: read on those repositories.VISE_GITHUB_PAT when the App is not
installed. The same token is handed to every session and used for all
server-side reads. Use a fine-grained PAT
restricted to the repositories vise works on, with Contents: read/write,
Pull requests: read/write and Actions: read. Fine-grained PATs cannot be
given the Checks permission (only GitHub Apps can), so check runs are read
through Actions: read instead.Both modes get identical behaviour: hosts obtain the credential through the
same endpoint, and PR tracking and follow-up sessions work the same way. If
both are configured, the App wins and the PAT is ignored (the server logs
this at startup). With neither, github_repo sessions fail and PR tracking is
disabled.
The server is open by default: a single-user install on localhost needs no
credential for vise sessions ... and vise hosts .... To require one, set
VISE_API_TOKEN on the server; user-facing routes (sessions and host
enrollment) then answer 401 unless the request carries
Authorization: Bearer <token>. The CLI sends it from --api-token,
VISE_API_TOKEN in the environment, or VISE_API_TOKEN in ~/.vise/.env.
Hosts are unaffected: the host protocol authenticates with the host's own
vhost_ token from enrollment.
Programmatically, vise-api resolves the caller through the
vise_api::auth::CallerExtractor trait held in AppState, so a composition
that embeds the API can swap in its own scheme (API keys, cookies).
The workspace is split into binaries you run and crates they share.
| Path | Kind | What it is |
|---|---|---|
bins/vise-server | binary | HTTP API, session scheduler, lease sweeper and PR tracker, backed by Postgres |
bins/vise-host | binary | Runs on a machine you enroll; claims sessions and drives the agent harness |
bins/vise-cli | binary | vise command-line client for sessions and hosts, and the vise host supervisor for the local host process |
crates/vise-api | library | axum routes, request/response types, OpenAPI document |
crates/vise-core | library | Domain model, session state machine, sqlx repositories and migrations (embedded, applied by the server on startup) |
crates/vise-client | library | Rust client generated at build time from openapi/openapi.json |
Longer design notes live in docs/.
See CONTRIBUTING.md for setup, conventions and the checks
CI runs. The short version is just check. Bugs and feature requests go
through GitHub issues; please read
SECURITY.md before reporting a vulnerability and
CODE_OF_CONDUCT.md before participating.
Licensed under either of
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Rust
89.2%
Shell
8.9%
Just
1.0%
vise runs coding-agent sessions against your repositories on hosts you control. You describe a task, the server schedules it onto an enrolled host, the host runs an agent harness (such as Claude Code) in an isolated workspace using the Agent Client Protocol, and the result comes back as a pull request, a pushed branch, or a live event stream you can tail from the CLI. The API is documented with OpenAPI and the Rust client is generated from that spec, so the CLI, the host and any integration you write all speak the same contract.
One line stands up the whole stack on a macOS (Apple Silicon) or Linux (x86_64, arm64) machine that has Docker and git:
curl -fsSL https://vise.sh/install | sh
vise.sh/install serves scripts/install.sh from this
repository. Without sudo, it:
docker (with the compose plugin) and git, and warns if
claude (Claude Code) is missing: the claude-code harness needs it, the
echo harness does not;VISE_GITHUB_PAT) and
writes ~/.vise/.env plus a ~/.vise/docker-compose.yml that runs Postgres
and ghcr.io/vise-sh/vise-server;docker compose up -d and waits for the API on http://localhost:3000;vise-cli and vise-host archives from the latest
GitHub release, checks them
against the SHA256SUMS.txt attached to that release, and puts them in
~/.vise/bin (add it to your PATH);vise-host in the background.It is safe to re-run: an existing ~/.vise/.env is kept unless you say
otherwise, and the binaries and server image are upgraded in place. Set
VISE_VERSION=v0.2.0 to pin a release, VISE_PORT to move the API, or
VISE_HOME to install somewhere other than ~/.vise; the header of the script
lists every override. Then:
vise sessions create "add a --json flag to the ls command" --repo owner/repo --watch
The host on this machine is a plain background process, managed with
vise host:
vise host status # running / not running (exit 1 when stopped)
vise host logs -f # tail ~/.vise/logs/host.log
vise host stop # SIGTERM via ~/.vise/host.pid
vise host start # reads VISE_HOST_TOKEN and VISE_URL from ~/.vise/.env
vise host start -- --keep-workspaces passes extra flags through to
vise-host. Server logs are at docker compose -f ~/.vise/docker-compose.yml logs -f.
vise host stop
docker compose -f ~/.vise/docker-compose.yml down -v # containers and the Postgres volume
rm -rf ~/.vise
and remove the ~/.vise/bin line from your shell profile.
Every release ships the server as a container image,
ghcr.io/vise-sh/vise-server (linux/amd64 and linux/arm64, tagged with the
version and latest), plus vise-cli and vise-host archives for macOS
(arm64) and Linux (x86_64, arm64) on the
releases page. With those you
need Docker and nothing else; the server applies its own database migrations
on startup.
git clone https://github.com/vise-sh/vise && cd vise
cp .env.example .env # set VISE_GITHUB_PAT (or the App settings, see below)
docker compose up -d # Postgres 17 + vise-server on http://localhost:3000
Swagger UI is at /docs. The image is public, so no docker login is needed
to pull it. With a GitHub App instead of a PAT, add the overlay that mounts the
private key:
docker compose -f docker-compose.yml -f docker-compose.github-app.yml up -d.
Release images are signed with cosign; SECURITY.md
shows how to verify a pulled image.
To run the server from source instead, you also need Rust (stable) and just:
just db-up # Postgres only
cargo run -p vise-server # applies migrations, then serves on :3000
Enroll a host and start it with the token that is printed (use the release
binaries or cargo run -p ... interchangeably):
vise-cli hosts create laptop
VISE_HOST_TOKEN=<token> vise-host
(cargo run -p vise-cli -- host start --bin target/debug/vise-host runs the
same thing in the background, with the token taken from VISE_HOST_TOKEN or
~/.vise/.env.)
Then create a session and watch it run:
vise-cli sessions create "add a --json flag to the ls command" \
--repo your-org/your-repo --watch
Use --harness echo to try the flow without a real agent, and
sessions ls / sessions events <id> to inspect what happened.
A session that ends by opening a pull request does not stop being useful
there. The server keeps polling the PR (every 60 seconds by default, see
VISE_PR_POLL_INTERVAL_SECS) until it merges or closes, and records a derived
snapshot on the session:
pr_status.state: review_pending, changes_requested, approved,
merged, closed, or sync_error when the PR became unreadable. The state
is reduced from GitHub's review list (latest review per reviewer wins, an
outstanding request for changes beats approvals, approvals on an older
commit are stale); raw review comments are never stored.pr_status.checks: pending, passing or failing, from the check runs
on the head commit.Every transition is appended to the session's event stream as a
pr_state_changed or checks_state_changed event, so sessions watch <id>
on a finished session tails the PR until it merges or closes, and
sessions ls / sessions get show the current state.
When a reviewer asks for changes, spawn a follow-up:
cargo run -p vise-cli -- sessions follow-up <session-id> \
--instructions "keep the public API stable" --watch
The follow-up inherits the parent's agent configuration, is checked out on the
PR's head branch so its pushes update the same PR, and receives the current
review threads (with file and line context) and failing check names in its
input, composed server-side at creation time. Its outcome is pr_updated;
tracking stays with the session that opened the PR, however many follow-ups
chain off it.
The tracker and the follow-up endpoint reuse the server's GitHub credential (App or PAT, see below), which needs to read pull requests and check runs in addition to the Contents: read/write that hosts need to push: Pull requests: read and Checks: read for a GitHub App, Pull requests: read and Actions: read for a fine-grained PAT.
github_repo sessions need the server to authenticate to GitHub, both to hand
hosts a token for cloning and pushing and to read pull requests for tracking
and follow-ups. Two options are supported:
VISE_GITHUB_APP_ID
and VISE_GITHUB_APP_PRIVATE_KEY_PATH. The server mints a short-lived
installation token scoped to the session's repository for every session.
The App needs Contents: read/write, Pull requests: read/write and
Checks: read on those repositories.VISE_GITHUB_PAT when the App is not
installed. The same token is handed to every session and used for all
server-side reads. Use a fine-grained PAT
restricted to the repositories vise works on, with Contents: read/write,
Pull requests: read/write and Actions: read. Fine-grained PATs cannot be
given the Checks permission (only GitHub Apps can), so check runs are read
through Actions: read instead.Both modes get identical behaviour: hosts obtain the credential through the
same endpoint, and PR tracking and follow-up sessions work the same way. If
both are configured, the App wins and the PAT is ignored (the server logs
this at startup). With neither, github_repo sessions fail and PR tracking is
disabled.
The server is open by default: a single-user install on localhost needs no
credential for vise sessions ... and vise hosts .... To require one, set
VISE_API_TOKEN on the server; user-facing routes (sessions and host
enrollment) then answer 401 unless the request carries
Authorization: Bearer <token>. The CLI sends it from --api-token,
VISE_API_TOKEN in the environment, or VISE_API_TOKEN in ~/.vise/.env.
Hosts are unaffected: the host protocol authenticates with the host's own
vhost_ token from enrollment.
Programmatically, vise-api resolves the caller through the
vise_api::auth::CallerExtractor trait held in AppState, so a composition
that embeds the API can swap in its own scheme (API keys, cookies).
The workspace is split into binaries you run and crates they share.
| Path | Kind | What it is |
|---|---|---|
bins/vise-server | binary | HTTP API, session scheduler, lease sweeper and PR tracker, backed by Postgres |
bins/vise-host | binary | Runs on a machine you enroll; claims sessions and drives the agent harness |
bins/vise-cli | binary | vise command-line client for sessions and hosts, and the vise host supervisor for the local host process |
crates/vise-api | library | axum routes, request/response types, OpenAPI document |
crates/vise-core | library | Domain model, session state machine, sqlx repositories and migrations (embedded, applied by the server on startup) |
crates/vise-client | library | Rust client generated at build time from openapi/openapi.json |
Longer design notes live in docs/.
See CONTRIBUTING.md for setup, conventions and the checks
CI runs. The short version is just check. Bugs and feature requests go
through GitHub issues; please read
SECURITY.md before reporting a vulnerability and
CODE_OF_CONDUCT.md before participating.
Licensed under either of
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Rust
89.2%
Shell
8.9%
Just
1.0%