magi-code 0.77.1

Repository-aware CLI coding agent for terminal work
Documentation
# Private Unix daemon launch contract

macOS and Linux use the existing `magi-code` binary. Stdio remains a separate,
cancel-on-EOF interface. Unix connections use persistent protocol v2; accepted
work belongs to the service, not the connection.

## Commands and identity

```sh
magi-code daemon start-or-connect --workspace /absolute/project \
  --state-root /absolute/shared/MC_HOME --executable /trusted/versions/0.76.0/magi-code
magi-code daemon status --workspace /absolute/project --state-root /absolute/shared/MC_HOME
magi-code daemon stop --workspace /absolute/project --state-root /absolute/shared/MC_HOME
magi-code daemon foreground --workspace /absolute/project --state-root /absolute/shared/MC_HOME
```

Workspace is required. Omitted `--state-root` resolves normal `MC_HOME` once.
The canonical workspace and canonical state root jointly identify the service;
symlink aliases converge, different state roots do not. Success prints a JSON
object containing `status`, `version`, `workspace`, `state_root`, and `socket`.
Start-or-connect returns discovery, not an attached stdio stream. A frontend must
open and authenticate its own socket connection. Paths must be JSON-encodable
UTF-8 for this transport. Normal independent CLI sessions remain available.
No connection changes global cwd or changes the daemon's project.

## Endpoint and wire handshake

A SHA-256-derived short name lives under `/tmp/magi-<effective uid>` (0700).
Socket and lifetime lock are private (0600). The lock inode is never removed.
Only the lifetime lock owner may remove a refused, safely revalidated stale
socket. Live, unsafe, and incompatible sockets are never replaced. Symlinks,
wrong types, foreign ownership, public modes, and multiply linked locks fail
closed. Both endpoints authenticate peer effective UID (`getpeereid` on macOS,
`SO_PEERCRED` on Linux). Root operation is unsupported. Same-user malware is
not an isolation boundary.

Before sending protocol v2, send one JSON line (maximum 4096 bytes):

```json
{"version":"0.76.0","workspace":"/canonical/project","state_root":"/canonical/state","action":"connect"}
```

The reply echoes `version`, `workspace`, `state_root`, and has `status` of
`ready`, `incompatible`, `busy`, or `stopped`. Validate the agreed identity and
exact installed version before reuse. Actions `status` and `stop` close after
this reply and never register as persistent clients. `connect` then requires
the normal v2 initialize request within five seconds. Connection/request IDs
are independent per client; use the initialize response's identities.

The adapter admits at most 32 connections including unfinished handshakes.
Connect, handshake, incomplete-frame, and output deadlines are five seconds.
Frames use the existing protocol record limit and bounded coordinator queues;
the adapter retains one outbound record and a bounded input frame per client.
Slow/dead clients disconnect without cancelling their accepted turns/settings.
No uncertain request is automatically replayed. Admission and idle stop are
serialized by the existing coordinator. Stop refuses connected clients and
admitted work, including auth/settings/cleanup. The core's startup grace and
zero-client/zero-work idle timeout also apply. Handshake-only peers cannot
keep the core alive indefinitely.

Readiness polling is bounded (15 seconds plus an in-flight bounded handshake).
Failure reports uncertainty, never kills a process. Inspect status before
retrying. A startup loser cannot bind while another process owns the lifetime
lock. Shutdown keeps that lock until listener, adapters, and core have finished.
A stuck cleanup is not permission to delete a lock, force-stop work, or start a
parallel version.

## Mission Control ownership and handoff

Standalone Mission Control and service attachments use the same per-session
exclusive writer lease. Startup, resume, new sessions, switching, and rewind
forks acquire it before writable replay, activation, or append. The order is
writer lease, shared retention/activation lease, then short-lived append locks.
Nested standalone operations reuse the admitted session; its worker clones keep
the writer lease until persistence and cleanup finish. Independent sessions in
the same workspace do not contend on this lease.

A busy or failed switch leaves the previous session and draft usable. There is
no forced unlock or live takeover. Idle controlled daemon sessions stay locked;
unowned sessions release automatically only after persistence, worker exit/join,
and terminal settlement. Cleanup delays continue to block standalone admission.

Picker and service-list ownership hints are advisory: `daemon-owned`, `busy`,
`unowned`, or `unknown`. Reads are capped at 256 bytes per evidence file and 128
picker entries (remaining entries show `unknown`). Missing lock evidence means
observed `unowned`; stale or invalid evidence means `unknown`. The daemon marker
contains only a version and lock-token digest, not process, workspace, or user
data. Painting a list never acquires a writer lease. Even a fresh hint may race
with ownership changes: final exclusive acquisition alone decides admission.

## Trusted installation and activation

Companion and Linux installers must stage a verified executable in a new,
version-specific directory before activation. Validate the release digest and
platform signing policy out of band; the daemon does not download or verify
release signatures. Launch an absolute, non-symlink, non-writable executable
(0555 is suitable), owned by the user or root, under trusted directories. Do
not launch a PATH lookup, shell command, mutable development build, or `current`
symlink. The CLI checks type, ownership, write bits, and parent directories;
these checks do not protect against malicious code already running as the user.

Never overwrite an activated executable. Keep it until its service has exited.
A compatible service is reused even if busy. A different version is refused:
no auto-kill, restart, forced upgrade, or parallel version for the same identity.
To activate, ask the old installed binary to stop; proceed only after idle stop
and confirmed exit, with frontend admissions paused. Then switch the client's
trusted executable selection. Staging alone must not switch it. Rollback follows
the same idle-only procedure.

## Process lifetime and environment

Lazy launch uses `Command` with an absolute executable and null standard streams,
then `setsid()` in the newly exec'd same-binary child before readiness. There is
no `pre_exec` callback or application code in a threaded parent's post-fork
child. Foreground development mode does not detach. The lazy child has an
independent session and does not depend on frontend stdin/stdout or EOF.

The launcher's environment is inherited, except `MC_HOME` is replaced with the
agreed canonical root. The child starts in the canonical workspace. No credential
values are placed in argv, discovery, or logs. Shared stored auth is read from
that root; environment-based provider keys remain the launch environment's
snapshot and are not changed by later connections. Existing logout semantics
cannot unset inherited environment keys.

GUI packaging must construct its launch environment explicitly: `HOME`, approved
locale/temp/certificate variables, a trusted tool `PATH`, and only intentionally
configured provider key variables. Do not import arbitrary project environment,
loader injection variables, shell startup scripts, or another app's credentials.
macOS Finder/LaunchServices launches do not inherit interactive shell exports;
prefer shared stored auth or explicitly configured environment auth. Linux
clients have the same responsibility. No credential values should appear in
activation records or error reports.

`setsid` is **not** a Linux cgroup escape. A terminal/session manager, container,
CI runner, or systemd unit configured to kill its whole cgroup can still kill
the daemon when the frontend exits. Supported independent lifetime requires a
launch context whose process/cgroup survives frontend termination. Optional
supervisor integration is deferred. Neither platform promises logout, reboot,
crash, or forced-session-kill continuation.

## Verification limits

`tests/unix_daemon_process.rs` uses isolated homes/state and no provider calls.
It exercises real same-binary processes, alias/long-path identity, root isolation,
concurrent launches, incompatible refusal, connected-client stop refusal,
disconnect, private modes, retained lock inodes, stale recovery, unsafe paths,
consecutive JSONL records, and recovery under file-descriptor exhaustion.
`src/service/unix/survival_tests.rs` uses test-only synthetic workers and real
processes: it kills the submitting frontend, verifies busy-stop refusal after
disconnect, then reconnects and checks committed completion and idle shutdown.
Earlier documentation reported macOS checks passing, but no run artifact for the
current release candidate is linked here. See the [release evidence matrix](persistent-backend-readiness.md)
for macOS/Linux CI suites, fixture revisions and unavailable gates. Cross-UID
rejection and deployment-specific cgroup survival remain unverified. Terminal
parent death is not evidence of survival under Linux cgroup teardown.