v0.20.0
Neo gains a long-lived presence: an out-of-band synthesis observer supervised by CAR's agent runtime, a live A2UI memory inspector that exposes both observer cycles and FactStore state to any conformant renderer, plus quieter foundational work on project-ID stability, metrics ergonomics, and a new fact-domain taxonomy.
Added
- Async synthesis observer (
neo memory observer {start|stop|status|kick}) — a per-project background process that runssynthesize_reviewson a wall-clock cadence (default 5 min), decoupled from the request path. Additive: the inline triple-trigger gate keeps firing too; the observer just makes synthesis more frequent. Lifecycle is owned by CAR's agent supervisor (car-runtime ≥ 0.17.0): spawn, restart-on-failure, log redirection to~/.car/logs/, clean SIGTERM shutdown, and auto-start at daemon boot via the persisted spec in~/.car/agents.json. Tunables:NEO_OBSERVER_INTERVAL_SECONDS,NEO_OBSERVER_COOLDOWN. Status surfaces CAR's raw state (running | stopped | starting | backoff | errored) so restart-loops are diagnosable. Hard footgun: the interpreter must not live under a world-writable directory (/tmp,/private/tmp,/var/tmp,/dev/shm) — the CAR daemon rejects such commands for security. - A2UI memory inspector surface (
neo.a2ui) — a per-project A2UI v0.9 surface (neo-<project_id8>) registered with the runningcar-serverdaemon so any conformant renderer (CarHost.app, future webviews) can inspect Neo's state live. Two tabs: Observer (status badge, pid, last cycle, recent cycles list, Kick/Stop buttons) and Memory (valid fact count, by kind, by scope, probation count). Both populate from the sameFactStoreload the observer's synthesis cycle already does — zero hot-path cost. Kick/Stop buttons emita2ui.actionnotifications which the observer dispatches tokick_observer/stop_observer, closing the loop with CAR's supervisor. Activation: auto when127.0.0.1:9100is reachable; silent no-op otherwise. Fact.domaintaxonomy — optional free-form area tag orthogonal toFactKind.memory.models.SUGGESTED_DOMAINS(code-style,testing,git,debugging,workflow,security,file-patterns,architecture,performance) is the recommended vocabulary; any string is valid.retrieve_relevant(..., domain=...)filters by exact match;domain=Nonereturns all facts including unset ones; a specific filter excludes domain-unset facts.NEO_PROFILEmetrics gating (off | minimal | standard | strict) replaces the single-knobNEO_METRICS=off.minimalemits onlylm_call(audit trail without per-retrieval volume);standardemits everything (default, matches prior behavior);strictis reserved for future verbose events.NEO_METRICS=offremains a hard kill-switch that overridesNEO_PROFILE.
Changed
project_idis now hashed from the normalized git remote URL (scope._compute_project_id), not the codebase root path. The same repo on different clones / worktrees / machines now resolves to the same project ID. Falls back to a path hash when no remote is configured. Legacy path-hashed fact and watermark files in~/.neo/facts/are renamed in place onFactStoreinit (store._migrate_legacy_project_id_files) — transparent for upgrades.car-runtime>=0.17.0is now the floor for the[car]extra (bumped from>=0.9.0). The observer'sagents_*lifecycle calls require the WS-routing fix that landed in v0.17.0 (Parslee-ai/car-releases#54) — earlier car-runtime versions still work forCarAdapter-only use, butneo memory observer startwill refuse them with an actionable error pointing at the upgrade path.
Fixed
- Observer's
agents_listshape parsing — the CARManagedAgentshape uses astatusstring (running | stopped | starting | backoff | errored), not arunningbool. Initial port checkedexisting.get("running")which was always falsy — meaningstop_observerreturned "not_running" before ever callingagents_stop, leaking the supervised child. Fixed;observer_statusnow surfaces CAR's raw status verbatim. Regression test added. asynciomissing from the observer module-scope import. Surfaced only after the daemon spawn — caught by the live A2UI integration test, not unit tests.a2ui.applyenvelope was wrapped underparams={"envelope": …}instead of being passed as the envelope directly, silently creating an empty surface. Wire-shape regression test added (tests/test_a2ui.py::TestWireShape).pytest-asyncioadded to[dev]extra — the new async a2ui tests failed in CI because the plugin was only in my local venv. Addspytest-asyncio>=0.21.0to the manifest.websockets>=12.0added to[car]extra — needed byneo.a2ui.DaemonClientto speak JSON-RPC over the daemon's WebSocket (the Pythoncar_runtime.a2ui_*helpers are in-process only).
Docs
- CLAUDE.md / AGENTS.md gain bullets for: project-ID hashing + migration, domain taxonomy, profile gating, observer (incl. tunables and the world-writable-command footgun), and the A2UI surface architecture (including why the in-process
car_runtime.a2ui_*helpers don't suffice). - New
docs/solutions/async-observer.mddesign proposal — borrows process-lifecycle ideas from ECC'scontinuous-learning-v2observer, ported onto CAR'sagents_*supervisor. Includes an as-built note pointing readers atsrc/neo/memory/observer.py.