Viva is Haisu's local-first personal AI collaboration system.
Several persistent AI members work through Viva — each with its own role, history and knowledge — and drive real tools to get work done. Viva is not an AI member: it preserves their working relationships and history.
Customer Zero is Haisu; Viva ships with no built-in personas.
Samuel,DevenandAliceare records you create — configuration data, never hard-coded identity.Canonical product definition and project knowledge: the GitHub Wiki — see Vision.
Resident ≠ Role ≠ Cognitive Engine ≠ Worker ≠ Execution Session
Workspace ≠ Project ≠ Repository ≠ Worktree ≠ Task
raw event ≠ experience ≠ memory ≠ self-model ≠ identity
Session dies. Resident persists.
viva is a single native binary (Rust + SQLite). All facts live in
$VIVA_HOME (default ~/.viva, 0700); member names and model/tool bindings
are configuration, and every completion claim carries recorded evidence.
- Foundation —
viva initcreates the home and applies the full Viva schema;viva doctorreports state, schema versions and table counts;viva event add/listpersists and reloads events across restarts. - Resident server —
viva server(aliasviva start) runs the single resident server for the home (a second start refuses and names the running one); it owns the store, every terminal and a private Unix-socket channel untilviva shutdownends it. Plainvivaattaches the workbench and spawns a detached server first when none runs. From any other process:viva status/dispatch/terminals/stop-terminal/result/handoff/shutdown. Dispatch is grant-checked at the moment of effect, idempotent by request key, and launches a real PTY in the task's worktree. With no server running, mutations are clean rejections; onlyvivaandviva serverstart one. On a crash, the next server reconciles: interrupted launches and orphaned executions get honest recovery records; nothing is re-run, nothing is killed, finished tasks refuse re-dispatch. - Parallel development workbench — one Viva instance spans projects, tasks,
worktrees and terminals (see
crates/viva/src/tui/workbench/): real git facts (branch/dirty/diff), purpose-terminals per worktree with focused keyboard input that never crosses neighbors.qdetaches the TUI while the server keeps every terminal running;viva shutdownstops owned processes while preserving every worktree and record. - Conversation metadata —
viva conversationskeeps the Viva-owned tree: display names, fork hierarchy, write-once native session pointers, explicit task attachment, and cross-harness handoffs with declared capability. The harness (Pi) owns the transcript; Viva owns the tree. - Pi as the default conversational host — members meet in Pi's own UI
inside a Viva terminal (
crates/viva/src/harness/pi/+ extensions/pi/). The extension injects the member's task context and offers status queries, grant-controlled dispatch and explicit handoff. External memory (F04) stores native facts in the Viva database with member/project scope, provenance, usage evidence, and recoverable archive (viva memory …; no Hermes checkout or Python required). Legacy Hermes link rows remain aslegacy_hermesfor provenance but do not recall. There is no automatic recall or write-back in chat. Since #101 the Pi memory tools register against the real Pi API; end-to-end Pi exercise is tracked separately. - Data preservation —
viva data export --out <dir>dumps every fact table read-only to JSON with a manifest. Historical Ticket Autopilot data is never read, written or migrated.
Not implemented, and never faked: automatic reflection, self-model evolution, automatic memory promotion, starting at login (planned, to be configurable), an automatic task-scheduling daemon (a required goal; today the server only runs scheduled recovery and auto-sync), automatic remote writes, multi-user support. Unavailable models/tools/providers fail loudly; Viva never substitutes one silently.
Requires macOS (Apple Silicon or Intel). External tools, when you use the
matching features: git (required for worktrees; fetched read-only), gh
(optional, GitHub evidence), Pi
(optional, member conversation host) and your own model-CLI credentials —
Viva never stores credentials.
From a release (no Rust toolchain needed): grab viva-macos-<arch>.tar.gz
from the releases page, unpack,
and run ./viva. Each artifact ships with a sha256 checksum and includes the
Pi extension sources and licenses.
With npm (prebuilt binary; needs Node ≥ 18):
npm i -g @zuohaisu/viva # installs the `viva` commandFrom source:
cargo install --path crates/viva # or: cargo build --release
viva init && viva doctorviva update --check # check the latest stable version without installing
viva update # upgrade this installationGlobal npm installs update through npm at the original prefix, keeping the
wrapper and platform package in sync. Local npm installs should use
npm install @zuohaisu/viva@latest from their project instead.
Standalone macOS binaries (including cargo install) update from the official
GitHub Release for their architecture: HTTPS download → SHA-256 verification
→ version probe → atomic executable replacement. Equal/older releases are not
installed; failed native downloads/validation leave the executable unchanged.
The installation directory must be writable; Viva never invokes sudo.
Cargo build outputs and directly invoked package-manager binaries are refused.
Native updates replace only the executable; refresh bundled Pi extension
sources separately from the matching release if you use them. Checksums verify
integrity, not an independent publisher signature.
Updates do not open/migrate VIVA_HOME or alter members/tasks/history.
After installing, a running resident server is restarted through the live
handoff: the new binary takes over and the terminals survive; reconnect the
TUI afterwards. viva update --no-restart skips this (viva server-restart
does it later); with no server running, nothing is started. Upgrades are
user-invoked, not automatic member maintenance actions.
Older releases without this command need a one-time npm reinstall or release
download before viva update becomes available.
viva update installs the latest stable GitHub Release or the npm latest
tag. It does not read a private file server. To click-test a commit without
publishing, serve that commit's archive from a Linux host and install it on
the Mac that runs the TUI. The Linux host is only a file server.
Publish this directory over HTTP or HTTPS:
install.sh
channel/viva-macos-arm64.tar.gz
channel/viva-macos-arm64.tar.gz.sha256
channel/viva-macos-intel.tar.gz
channel/viva-macos-intel.tar.gz.sha256
Pack an archive the same way as .github/workflows/release.yml. On the Mac,
from a checkout of the commit under test:
cargo build --release -p viva
DIR=viva-macos-arm64 # Intel: viva-macos-intel
mkdir -p "$DIR/extensions"
cp target/release/viva "$DIR/viva"
cp -R extensions/pi "$DIR/extensions/pi"
rm -rf "$DIR/extensions/pi/node_modules"
cp LICENSE README.md "$DIR/"
tar -czf "$DIR.tar.gz" "$DIR"
if command -v shasum >/dev/null 2>&1; then
digest=$(shasum -a 256 "$DIR.tar.gz" | awk '{ print $1 }')
else
digest=$(sha256sum "$DIR.tar.gz" | awk '{ print $1 }')
fi
printf '%s %s\n' "$digest" "$DIR.tar.gz" > "$DIR.tar.gz.sha256"Publish the architectures you built. Copy the archive and the checksum into
channel/. The checksum line is the digest, two spaces, then the archive
name. Take the digest from shasum or from sha256sum and write the line
with printf, as above. Do not redirect sha256sum output unchanged:
binary mode writes digest *name, and macOS shasum -c rejects a line whose
spacing is not its own.
On a Tailscale network, bind the server to that node's 100.x.x.x address and
serve plain HTTP. A bare IP has no trusted HTTPS certificate, so
https://100.x.x.x fails TLS verification. Tailscale already encrypts the
path. Do not listen on 0.0.0.0 when the machine also has a public address.
Caddy, with the http:// prefix so it does not request a certificate:
http://100.x.x.x {
root * /var/www/viva-test
file_server
}
install.sh on that host. It contains no server address. Set
VIVA_TEST_BASE to the origin you curl, with no trailing path:
#!/bin/sh
set -eu
if [ -z "${VIVA_TEST_BASE:-}" ]; then
printf 'Set VIVA_TEST_BASE to the file server origin, with no trailing path\n' >&2
exit 1
fi
BASE=${VIVA_TEST_BASE%/}
case "$(uname -s)-$(uname -m)" in
Darwin-arm64) ASSET=viva-macos-arm64.tar.gz ;;
Darwin-x86_64) ASSET=viva-macos-intel.tar.gz ;;
*) printf 'macOS Apple Silicon or Intel only\n' >&2; exit 1 ;;
esac
if [ "$(id -u)" -eq 0 ]; then
printf 'run as your user, not root\n' >&2
exit 1
fi
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
curl -fsSL "$BASE/channel/$ASSET" -o "$tmp/$ASSET"
curl -fsSL "$BASE/channel/$ASSET.sha256" -o "$tmp/$ASSET.sha256"
expected=$(awk 'NR==1 && $1 ~ /^[0-9a-fA-F]{64}$/ { print tolower($1); exit }' "$tmp/$ASSET.sha256")
if [ -z "$expected" ]; then
printf 'checksum file must start with a 64-hex SHA-256\n' >&2
exit 1
fi
actual=$(shasum -a 256 "$tmp/$ASSET" | awk '{ print tolower($1) }')
if [ "$expected" != "$actual" ]; then
printf 'SHA-256 mismatch\n' >&2
exit 1
fi
dest="$HOME/.local/viva-test"
rm -rf "$dest"
mkdir -p "$dest"
tar -xzf "$tmp/$ASSET" -C "$dest" --strip-components=1
chmod 755 "$dest/viva"
printf 'Installed %s\n' "$dest/viva"
printf 'export VIVA_HOME="$HOME/.viva-test"\n'
printf '%s\n' "$dest/viva"On the Mac, with Tailscale connected (the same origin in both places):
curl -fsSL http://100.x.x.x/install.sh | VIVA_TEST_BASE=http://100.x.x.x sh
export VIVA_HOME="$HOME/.viva-test"
~/.local/viva-test/vivaThe script replaces ~/.local/viva-test only. It does not change ~/.viva
or a viva already on PATH. If a test server is already running, stop it
with the same VIVA_HOME before installing again:
VIVA_HOME="$HOME/.viva-test" ~/.local/viva-test/viva shutdownReplacing the files in channel/ publishes the next build. Run the same curl
command again. Leave install.sh unchanged when the origin changes or a new
archive is published: the origin is only in that command, and the script
trusts the checksum file's first field. It compares that digest to
shasum -a 256 of the downloaded archive, so a GNU sha256sum line
(digest *name or digest name) still verifies. viva update still
follows the stable channels, so do not run it against ~/.local/viva-test.
A PR build can still print the version in Cargo.toml; record
git rev-parse HEAD with the bug report.
Run Viva:
viva # attach the workbench (starts the resident server if needed)
viva status # from any second terminal/process
viva shutdown # stop the resident server and its owned processesProject documentation — product definition, architecture, roadmap, research, decisions (ADRs), development records, validation records and release history — is maintained in the GitHub Wiki, Viva's canonical documentation and project knowledge source:
https://github.com/zuohaisu/viva/wiki
Start with Home and Roadmap. The working charter for agents — including how to read and update the Wiki — is AGENTS.md.
The approved target is Rust + Ratatui/Crossterm for the Viva host and
terminal surface, Pi + a small TypeScript extension for member
conversation, and SQLite + ordinary files for Viva state.
ADR 0011 is the authoritative
selection. The Python runtime was retired in V13
(retirement record
maps every retired behavior to its Rust evidence); the Rust binary is the only
viva entry.
crates/viva/src/
foundation/ ids, paths, store (SQLite/migrations), events, envelope, records
members/ workspaces/ projects/ configuration and context domains
tasks/ task registry, launch-intent protocol, results, briefs
authority/ redaction/ grants, denial ledger, redaction policy
terminal/ real PTY sessions (spawn/input/resize/snapshot/stop/wait)
git/ worktrees (discover/create/adopt), read-only GitHub evidence
knowledge/ sources, lifecycle, selection, usage evidence
conversations/ tree, forks, handoffs (V10)
server/ active host, control channel, reconciliation (V07)
harness/ explicit launch combinations; Pi specialization (V09)
tui/ shell, parallel-development workbench (V14), conversations
extensions/pi/ the Pi member extension (TypeScript, independently tested)
cargo test --workspace # Rust: units + per-issue acceptance tests
python -m pytest tests/acceptance -q # acceptance tooling (stdlib-only)
cd extensions/pi && npm ci && npm run typecheck && npm test # Pi extensionAny agent can read the project-specific QA prompt in a fresh session:
读取
.agents/prompts/independent-qa.md,按其中要求对 PR #编号进行独立 QA;只读审核,不修改代码,报告发现、证据和未验证项。
This is an on-demand review instruction, not an automatic CI gate or approval.
Members and workers never gain repository-owner authority: no protected-branch
pushes, no merges, no approving their own PR, no self-authorization — the
vocabulary lives in crates/viva/src/authority/ and protected actions are
refused to every grant. Caller roles exist only because Viva issued a
channel; nothing self-declares. Every persisted artifact is private
(0o600/0o700) and secret-redacted before it is written. Viva never reads,
writes or migrates the historical ~/.ticket-autopilot/ state.
Viva is an original implementation, and its design deliberately references two proven tools:
- herdr (Apache-2.0) — the mature terminal-multiplexer reference behind Viva's resident runtime and workbench chrome: a resident server that holds all terminal sessions with detach/attach, a workspace/tab/pane layout, mouse-first pane interaction, and sidebar-based agent awareness. Viva implements these ideas natively ("route A"): no herdr source code is copied or redistributed. The alignment analysis and decision records live in the Wiki (Research-Herdr-Runtime-Parity and ADR-0012).
- Orca (MIT) — the day-to-day workbench experience reference for running several agents in parallel across git worktrees. Also a design reference only; no source code is reused.
Both projects are credited as design references. If any code or substantive material from either project is ever reused, the respective license and copyright notices will be preserved in full, as those licenses require.
MIT — see LICENSE. Copyright (c) 2026 zuohaisu