P0 — add long-lived zara-server lifecycle and principal-aware runtime supervisor #128

Closed
opened 2026-08-21 23:40:27 +00:00 by lost-rob0t · 1 comment
lost-rob0t commented 2026-08-21 23:40:27 +00:00 (Migrated from github.com)

Parent epic: #127
RAGE task: #139
Research baseline: docs/research/001-daemon-zeromq-voice-service.md

Goal

Create the process/lifecycle foundation for a long-lived Zara service. The executable boundary is now explicit:

  • zara remains the client-facing command;
  • zara-server is the long-lived daemon/service process.

This slice establishes lifecycle and the client/service seam without pretending the ZeroMQ transport from #129 already exists.

Scope

  • Add zara-server as the canonical long-lived headless service executable.
  • Keep zara as the client name. Do not add zara --daemon as the canonical interface.
  • Introduce a ZaraClient-facing abstraction so supported callers do not depend directly on RuntimeHost; #129 will later place the ZeroMQ implementation behind that boundary.
  • Reuse/extend zara.runtime.RuntimeHost; do not wire the service directly to AgentManager, Prolog, tools, memory, TTS, or Qt.
  • Introduce a server supervisor boundary capable of owning principal-scoped runtime contexts later.
  • Define explicit states: starting, ready, degraded, stopping, stopped, failed.
  • Preserve current standalone/headless behavior during migration.
  • Ensure server shutdown drains/cancels active turn work and plugin/runtime resources deterministically and joins owned worker threads before process exit.
  • Add deterministic single-instance ownership using an owner-private runtime directory/lease rather than trusting stale PID files.
  • Do not create a second event/command vocabulary; use #82/#83 contracts.

Architecture requirements

  • Principal ownership must be explicit in supervisor APIs even before authentication lands.
  • No process-global current_user or current_session shortcut.
  • PrologEngine uses one process-wide PySWIP/SWI runtime under a lock, so this slice must not claim thread-per-user equals hard tenant isolation. Production multi-user enablement remains gated by #130/#131.
  • The server owns runtime lifecycle. Clients own presentation/device concerns.
  • The future ZeroMQ gateway must terminate at the client/service boundary, not at AgentManager/Prolog internals.

Tests

  • zara-server starts and reports ready without Qt;
  • duplicate server ownership fails deterministically;
  • startup failure/degraded state;
  • clean shutdown with no active work;
  • shutdown with active turn(s);
  • stale work cannot emit after shutdown;
  • supervisor APIs require an explicit principal identity;
  • two synthetic principal context objects can coexist without sharing supervisor bookkeeping, while tests/docs explicitly avoid claiming persistence/auth isolation before #131;
  • ZaraClient boundary can drive a local/in-process runtime implementation without exposing RuntimeHost to its caller;
  • existing zara, wake, desktop, console, and dictation imports remain functional;
  • packaged zara-server wrapper exists;
  • focused non-interactive script plus full repo/Nix gate.

Acceptance

  • zara-server is a real long-lived process with deterministic ownership and shutdown.
  • zara remains the client executable name.
  • server lifecycle is separate from any GUI/client process.
  • callers can target a ZaraClient abstraction instead of RuntimeHost internals.
  • existing RuntimeHost/TurnCoordinator semantics are reused.
  • process model is ready for #129 ZeroMQ transport and #130/#131 authenticated isolation without claiming those later guarantees early.

Dependencies

No remote public listener, authentication, or voice transport is required in this slice.

Parent epic: #127 RAGE task: #139 Research baseline: `docs/research/001-daemon-zeromq-voice-service.md` ## Goal Create the process/lifecycle foundation for a long-lived Zara service. The executable boundary is now explicit: - `zara` remains the client-facing command; - `zara-server` is the long-lived daemon/service process. This slice establishes lifecycle and the client/service seam without pretending the ZeroMQ transport from #129 already exists. ## Scope - Add `zara-server` as the canonical long-lived headless service executable. - Keep `zara` as the client name. Do not add `zara --daemon` as the canonical interface. - Introduce a `ZaraClient`-facing abstraction so supported callers do not depend directly on `RuntimeHost`; #129 will later place the ZeroMQ implementation behind that boundary. - Reuse/extend `zara.runtime.RuntimeHost`; do not wire the service directly to AgentManager, Prolog, tools, memory, TTS, or Qt. - Introduce a server supervisor boundary capable of owning principal-scoped runtime contexts later. - Define explicit states: starting, ready, degraded, stopping, stopped, failed. - Preserve current standalone/headless behavior during migration. - Ensure server shutdown drains/cancels active turn work and plugin/runtime resources deterministically and joins owned worker threads before process exit. - Add deterministic single-instance ownership using an owner-private runtime directory/lease rather than trusting stale PID files. - Do not create a second event/command vocabulary; use #82/#83 contracts. ## Architecture requirements - Principal ownership must be explicit in supervisor APIs even before authentication lands. - No process-global `current_user` or `current_session` shortcut. - `PrologEngine` uses one process-wide PySWIP/SWI runtime under a lock, so this slice must not claim thread-per-user equals hard tenant isolation. Production multi-user enablement remains gated by #130/#131. - The server owns runtime lifecycle. Clients own presentation/device concerns. - The future ZeroMQ gateway must terminate at the client/service boundary, not at AgentManager/Prolog internals. ## Tests - `zara-server` starts and reports ready without Qt; - duplicate server ownership fails deterministically; - startup failure/degraded state; - clean shutdown with no active work; - shutdown with active turn(s); - stale work cannot emit after shutdown; - supervisor APIs require an explicit principal identity; - two synthetic principal context objects can coexist without sharing supervisor bookkeeping, while tests/docs explicitly avoid claiming persistence/auth isolation before #131; - `ZaraClient` boundary can drive a local/in-process runtime implementation without exposing RuntimeHost to its caller; - existing `zara`, wake, desktop, console, and dictation imports remain functional; - packaged `zara-server` wrapper exists; - focused non-interactive script plus full repo/Nix gate. ## Acceptance - `zara-server` is a real long-lived process with deterministic ownership and shutdown. - `zara` remains the client executable name. - server lifecycle is separate from any GUI/client process. - callers can target a `ZaraClient` abstraction instead of RuntimeHost internals. - existing RuntimeHost/TurnCoordinator semantics are reused. - process model is ready for #129 ZeroMQ transport and #130/#131 authenticated isolation without claiming those later guarantees early. ## Dependencies - #82 - #83 - #127 No remote public listener, authentication, or voice transport is required in this slice.
lost-rob0t commented 2026-08-22 05:08:15 +00:00 (Migrated from github.com)

RAGE design refinement from TASK #139: structure #128 around a client-facing service boundary instead of exposing RuntimeHost as the daemon API.

Proposed split:

  • ZaraDaemon = process lifecycle, single-instance lease, signals, readiness/shutdown.
  • RuntimeSupervisor = principal -> owned runtime slot(s), explicit lifecycle and event ownership.
  • ZaraClient protocol/interface = the only user-facing command/event contract.
  • LocalZaraClient = in-process adapter for tests/standalone compatibility.
  • #129 implements ZmqZaraClient + daemon gateway over ZARA/1 without changing client semantics.
  • #133 later migrates CLI/Desktop to the same ZaraClient API.

This preserves issue ordering: #128 defines the boundary and local adapter, #129 supplies the transport implementation, #133 switches surfaces over. It also prevents CLI/Desktop from depending directly on RuntimeHost or daemon internals.

RAGE design refinement from TASK #139: structure #128 around a client-facing service boundary instead of exposing `RuntimeHost` as the daemon API. Proposed split: - `ZaraDaemon` = process lifecycle, single-instance lease, signals, readiness/shutdown. - `RuntimeSupervisor` = principal -> owned runtime slot(s), explicit lifecycle and event ownership. - `ZaraClient` protocol/interface = the only user-facing command/event contract. - `LocalZaraClient` = in-process adapter for tests/standalone compatibility. - #129 implements `ZmqZaraClient` + daemon gateway over `ZARA/1` without changing client semantics. - #133 later migrates CLI/Desktop to the same `ZaraClient` API. This preserves issue ordering: #128 defines the boundary and local adapter, #129 supplies the transport implementation, #133 switches surfaces over. It also prevents CLI/Desktop from depending directly on `RuntimeHost` or daemon internals.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
nsaspy/zara#128
No description provided.