-
Notifications
You must be signed in to change notification settings - Fork 1
self update
evi update lets a user pull a newer eVi version, verify it works, and
roll back if it doesn't. This doc is the design — none of it is built
yet. Once approved, it becomes Phase 29.
-
One-command upgrade.
evi updateis the only command a user should need. -
Always rollback-able. Every upgrade snapshots the prior
pip freezeso we can restore an exact known-good state. -
Refuse to clobber dev work. Editable installs (
pip install -e .) are never touched — we detect and refuse. - Channels later. Stable PyPI is the default. Pre-release + custom index URL come in a follow-up.
- Don't surprise locked envs. Poetry / uv / pipenv projects get a clear "use your tool's upgrade command" message instead of a silently-broken venv.
- Auto-updating the Tauri desktop binary — Tauri has its own updater; that's a separate phase.
- Updating dependencies independently of eVi (only pinned-via-extras matter for our use cases).
- Background / daemon checks. Update is always user-initiated.
| Command | Purpose |
|---|---|
evi update |
Check, show diff, prompt, upgrade. |
evi update check |
Check only — no install. |
evi update --yes |
Non-interactive upgrade. |
evi update --to <version> |
Pin an explicit version (downgrade allowed). |
evi update rollback |
Restore the most recent snapshot. |
evi update rollback <n> |
Restore the n-th most recent (1-indexed). |
evi update rollback <id> |
Restore a named snapshot dir. |
evi update history |
List snapshots. |
evi update prune --keep <n> |
Manual GC of old snapshots. |
evi update from-wheel <path> |
Offline install from a local wheel/sdist. |
evi update settings |
Print channel + snapshot retention. |
~/.evi/snapshots/
2026-05-27T14-03-00_0.10.0_to_0.11.0/
requirements.txt # pip freeze BEFORE the upgrade
version.txt # "0.10.0"
metadata.json # {"timestamp", "from", "to", "command"}
2026-06-02T09-12-44_0.11.0_to_0.11.1/
…
Snapshots are append-only. Default retention: last 5. A prune
runs at the end of each successful upgrade to drop older ones.
┌──────────────────┐
│ evi update │
│ (or --check) │
└─────────┬────────┘
│
▼
┌──────────────────┐
│ Probe PyPI │ /pypi/evi-assistant/json
└─────────┬────────┘
│
current ≥ latest? ──── yes ──► "up to date"
│
no
│
▼
┌──────────────────┐
│ Detect install │ editable? pipx? venv? locked-env?
│ kind │
└─────────┬────────┘
│
editable / locked-env / unknown
│
▼
┌──────────────────┐
│ Refuse + hint │ message tailored to install kind
└──────────────────┘
venv (the happy path)
│
▼
┌──────────────────┐
│ Take snapshot │ pip freeze + metadata
└─────────┬────────┘
│
▼
┌──────────────────┐
│ pip install -U │ --upgrade evi[==<version>]
└─────────┬────────┘
│
exit 0?
│
┌─────────┴────────┐
│ │
no yes
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ Surface stderr │ │ Verify import │ subprocess: python -c "import evi"
│ + snapshot kept │ └─────────┬────────┘
│ + hint rollback │ │
└──────────────────┘ version matches?
│
┌───────┴────────┐
│ │
no yes
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ Auto-rollback? │ │ GC old snapshots│
│ Or warn + hint │ │ + done │
└──────────────────┘ └──────────────────┘
We check, in order:
-
Editable — parse the output of
pip show evi-assistant. If it has anEditable project location:line, refuse with the editable location printed and a hint togit pullinstead. -
pipx — if
PIPX_HOMEis in env ORwhich eviresolves under~/.local/pipx/venvs/evi-assistant/, suggestpipx upgrade evi-assistantrather than running pip ourselves. -
Locked env — if cwd (or any parent up to
$HOME) has apoetry.lock/uv.lock/Pipfile.lock, suggest the right tool's upgrade command. The user can pass--forceto override. -
Plain venv / system pip — the happy path. Run
pip install --upgrade.
subprocess.run(
[sys.executable, "-m", "pip", "install", "--upgrade", spec],
check=False, capture_output=True, text=True, timeout=600,
)spec is either evi-assistant (latest) or evi-assistant==<version>. sys.executable
guarantees we install into the SAME interpreter that's running evi,
which is critical — using bare pip could pick up a different venv.
If --from-wheel <path> is used, spec becomes the path; pip handles
wheels and sdists transparently.
After pip exits 0, we spawn:
python -c "import evi; print(evi.__version__)"If that fails or returns the OLD version, we either:
- Interactive: ask the user "broken — roll back? [Y/n]"
-
--yes: roll back automatically and exit non-zero with a clear error.
This catches the case where pip happily "succeeded" but the installed package can't import (e.g. a dep version conflict that pip didn't catch because eVi doesn't pin it strictly).
subprocess.run(
[sys.executable, "-m", "pip", "install", "-r", snapshot / "requirements.txt"],
check=True, timeout=600,
)Then re-verify the import.
Note: we restore ALL frozen packages, not just eVi. That's intentional
— if the failure was caused by a transitive bump, just reinstalling
eVi at the old version won't help. Restoring the full pip freeze is
the only way to get a deterministic rollback.
Risk: if the user installed something else in the venv between the
snapshot and now, the rollback wipes that. Mitigation: print the
requirements.txt diff before running and prompt to confirm. With
--yes, skip the prompt — the user explicitly opted in.
evi/
update.py
─ check_pypi() → LatestInfo {version, release_url, changelog_url}
─ detect_install_kind() → "editable" | "pipx" | "locked" | "venv"
─ Snapshot dataclass {dir, timestamp, from_version, to_version}
─ create_snapshot(from_version, to_version) → Snapshot
─ list_snapshots() → list[Snapshot]
─ apply_upgrade(spec, *, dry_run=False) → UpgradeResult
─ apply_rollback(snapshot) → RollbackResult
─ verify_install() → (ok, installed_version, err)
─ gc_snapshots(keep=5) → list[deleted]
evi/apps/cli/main.py
─ update_app = typer.Typer()
─ @update_app.command("check") → check_pypi() + render
─ @update_app.command() → check + prompt + apply_upgrade
─ @update_app.command("rollback")
─ @update_app.command("history")
─ @update_app.command("prune")
─ @update_app.command("from-wheel")
─ @update_app.command("settings")
- Mock
httpx.get("https://pypi.org/pypi/evi-assistant/json")to return canned payloads (newer / same / older / 404). - Mock
subprocess.runfor pip calls — verify command shape, capture exit codes, simulate success + failure. - Real
tmp_path-rooted snapshot dirs to exercise create / list / gc / rollback dispatch. -
detect_install_kindagainst syntheticpip showoutput.
- Should rollback be one-step or stepped? Right now I propose "restore the full pip freeze". Alternative: only downgrade eVi itself, leave deps alone. Cleaner but doesn't recover from transitive bumps. I lean toward "full freeze, with a confirmation".
-
Where do PyPI release notes come from? The PyPI JSON API exposes
description(the README of that release). We could surface that verbatim, or link to GitHub's release page. Probably both. -
Auto-update via the scheduler? Could plug into the existing
evi/scheduler.py. Defer — feels too aggressive without a "stable for N days" policy first. -
Versioning of snapshots themselves. Right now I propose a
directory-name convention; a small
snapshots.jsonindex would makelistfaster and more reliable. Worth doing from day 1.
- Implementation: ~400 LOC in
evi/update.py, ~200 LOC of CLI wiring. - Tests: ~300 LOC.
- Total: a short phase, comparable to Phase 22 (distribution tooling).
- No new deps — uses stdlib
subprocess,httpx(already in core), and the user's installedpip.
Generated from docs/self-update.md — edit there, not here.
Start here
Guides
- Architecture
- [[Agent SDK (
evi.sdk)|sdk]] - SDK coverage + borrowable features
- Multi-machine setup
- Self-update design (Phase 29 proposal)
- [[Self-build — developing and building eVi with eVi|self-build]]
- Development notes
- Releasing
- Desktop bundling
- Code signing policy
- Surface parity — CLI ↔ Web ↔ Desktop
- eVi vs Claude Code — feature comparison
- Future integrations — backlog
- Roadmap
Feature deep-dives
- eVi feature guides
- Agents & Orchestration
- Recipes, Routines, Scheduled tasks, Channels
- Evals & LLM-as-judge
- Content Guardrails
- Hooks (tool + lifecycle, command/url)
- MCP (client + serve)
- Memory & Context management
- Observability (OpenTelemetry, stats, crash reports)
- Permissions & Sandbox
- Plugins & Marketplace
- Sessions, Resume, Handoff, Checkpoints
- Skills
- Slash commands
- Structured Outputs & Batch
- Ultracode
- Voice (TTS engines, STT, AutoSpeaker)
- Web & Desktop (settings, multi-user, deep links, updater)