# AgentTeam Daemon

> Canonical path: `specs/daemon/daemon.md`
> Scope: first-party daemon product contract, server-facing runtime ownership, daemon runtime state boundaries, and daemon-facing compatibility constraints.

## Product Position

The first-party AgentTeam daemon is the `agentteam daemon` command implemented in `desktop/`.
It is a headless Go process on macOS, Windows, and Linux. It does not own a
window, tray, renderer, or browser UI. macOS and Windows ship an Electron
application that starts or attaches to this daemon; Linux ships the Go
daemon/CLI only.

Supported entry points:

- `agentteam daemon` starts the local daemon from saved Desktop configuration.
- `agentteam auth login --api-key <machineApiKey>` resolves and saves AgentTeam machine auth without requiring the user to provide a workspace ID. If `--server-url` is omitted, Desktop uses the default AgentTeam SaaS URL, `https://agentteam.pingcap.com`.
- `agentteam auth status` and `agentteam auth logout` inspect and remove saved AgentTeam auth entries by local auth ID.
- `agentteam daemon --server-url <url> --api-key <machineApiKey>` resolves the same auth as `agentteam auth login`, persists it, and then starts the daemon through an explicit CLI path.

Machine auth resolution is server-owned. Desktop must exchange a browser one-time token or resolve a machine API key with AgentTeam Server before saving a local auth entry. The resolved identity must include `server_url`, `workspace_id`, `machine_id`, and `api_key`. Local Desktop auth entries are keyed by normalized `server_url` plus `workspace_id`: the same server/workspace updates the existing entry, while the same server with another workspace creates a separate entry. New local IDs use the `srv_` prefix; IDs migrated from legacy channel configuration are preserved.

The Electron Desktop application has a separate user-authenticated onboarding
path backed by daemon APIs. Its authentication state shows the official AgentTeam
server (`https://agentteam.pingcap.com`) first and offers another-server address input
on the same surface. It uses PingCAP Auth or password authentication according
to the probed server capabilities. After authentication, the
Workspace-selection state groups every available Workspace by saved server
account and lets the user either activate one Workspace or sign in to another
server. When the current user has no Machines in an activated or automatically
restored Workspace, Desktop creates the current computer as that user's first
Machine and waits for its first daemon WebSocket handshake before Workspace
entry completes. This automatic path reuses the existing server
Machine-creation and daemon machine-auth persistence contracts without a
deep-link credential exchange.

GUI user credentials and daemon machine credentials are separate contracts:

- `desktop/internal/userauth` owns GUI server accounts and stores them in `auth.yaml` by server account, with Workspace selection encoded in the active GUI target rather than duplicated credentials. It may persist the opaque last-selected target ID so Desktop can restore the same server and Workspace on the next launch.
- Passwords are request-scoped and are never persisted. Access tokens remain process-memory-only. Refresh tokens are encrypted with the existing device key before persistence, and `auth.yaml` is restricted to mode `0600`.
- `desktop/internal/profile` continues to own `servers.yaml` machine-auth entries for CLI and headless use. Existing Web Machine creation/connect-token exchange and CLI `machineApiKey` flows remain unchanged.
- Desktop GUI validates matching local machine credentials against AgentTeam Server before reporting or reusing them. If the server no longer recognizes a credential, Desktop removes that stale `servers.yaml` entry through the profile runtime transaction so the corresponding daemon connection is disconnected. Automatic activation creates a replacement only when the current GUI user has no remaining Machines in that Workspace.
- A compatibility entry with `server_url`, `workspace_id`, and `api_key` but no `machine_id` is unresolved rather than absent. Before any automatic or explicit current-computer creation, Desktop resolves that credential and patches the same local entry with the server-owned Machine ID. An unresolvable credential or mismatched Workspace preserves the local entry and blocks creation until the user restores an existing Machine or explicitly confirms replacement; a transient server failure preserves the entry and only blocks the current attempt so a later retry can resolve it.
- A GUI-selected Workspace with existing Machines may use shared server-backed product UI without binding the current computer. Desktop's explicit Machine dialog reports whether the current computer is already bound; it can add the current computer directly only when no complete or unresolved compatibility binding exists, while other machines use the existing CLI/API-key setup path.

The daemon owns local runtime process lifecycle, local work directories, runtime session continuity, runtime activity, trajectory/recovery state, and runtime-facing tool bridges. AgentTeam Server owns machine identity, durable agent/workspace/message/task state, orchestration commands, notifications, and diagnostics.

When AgentTeam Server rejects `/api/v1/workspaces/:workspaceId/daemon/connect` with `401 {"error":"invalid machine api key"}`, the local machine credential no longer represents an active server-side machine. The daemon must stop retrying that credential and wait for the user to reconnect through Web or CLI. A saved profile without `workspace_id` must also stop before connecting and tell the user to reconnect or log in again. Network errors, 5xx responses, and non-authentication handshake failures may use the normal reconnect strategy.

## Runtime Receipt, Control, And UI Sessions

Every daemon instance writes one atomically replaced, permission-restricted
runtime receipt in the per-user AgentTeam runtime directory. The receipt is the
canonical discovery record for CLI status and Electron attachment. It contains
the receipt/API schema version, daemon instance ID, PID plus process-start
identity, exact numeric loopback base URL, owner kind and process identity, and
a random control capability. Process-start identity uses a stable native
lifetime marker when the platform provides one; Linux combines the boot ID with
the process start ticks so wall-clock corrections cannot invalidate a live
daemon.

`desktop/internal/daemoncontrol` owns receipt validation, stale-receipt
reconciliation, current owner state, adoption, and shutdown authorization.
Consumers must verify both native process identity and the authenticated daemon
control identity. PID-only discovery, attachment, adoption, or shutdown is
forbidden.
Reconciliation may recognize a retired v1 receipt only to remove it after its
recorded daemon process identity is proven stale; ordinary receipt reads remain
strictly v2-only.

The private `/api/internal/control/v2` plane supports only daemon identity,
renderer UI-session create/ensure/renew/revoke, orphaned Electron-owner
adoption, and owner-safe graceful shutdown. Its control capability is accepted
only in the dedicated control header. It must not appear in command arguments,
environment variables, logs, URLs, renderer bootstrap/storage, or renderer
IPC.

Renderer UI sessions are short-lived, revocable bearer credentials scoped to a
daemon instance and Electron window. The daemon owns their stable logical
identity and bearer lease generations. Electron main is the sole lifecycle
owner: it reconciles the lease through the control plane before bootstrap, on a
main-process timer, after system resume or screen unlock, on window focus, and
after a typed renderer authentication failure. Reconciliation is idempotent and
may reissue an expired bearer for the same recoverable logical session without
losing its active-server selection. The renderer receives no owner capability.
Active server selection and its generation belong to the logical authenticated
UI session, not to a bearer value or the daemon process.
When a bearer generation is replaced, the previous bearer remains valid only
until its original expiry so already-dispatched requests can complete. Every
generation maps to the same logical session, and logical-session logout or
window revocation removes all of them.

Electron may attach to a compatible daemon owned by a CLI or service, but it
does not thereby own or gain permission to stop it. An orphaned
Electron-owned daemon may be adopted only after the old owner PID plus
process-start identity is proven dead; adoption atomically rotates the owner
identity and control capability.
Electron recognizes the retired v1 receipt only for authenticated sidecar
replacement. An orphaned Electron-owned v1 daemon is adopted, shut down, and
replaced with the packaged daemon before any renderer session is issued. On
macOS, a CLI/service receipt may also be migrated when the current user's
canonical AgentTeam LaunchAgent plist, loaded job PID, profile, process identity, and
authenticated control identity all match. A v1 service is migrated for API
compatibility. A compatible v2 service is migrated only when the packaged AgentTeam
SemVer is strictly newer than the canonical managed binary; equal, newer,
unknown, or non-canonical external daemons are left unchanged. Electron runs the
packaged `agentteam service reinstall`, waits for the v2 service identity, and
attaches without entering its own spawn path. Other external v1 daemons are
never replaced. If an old Electron owner is still alive, startup asks the user
to quit that application and retry. This canonical LaunchAgent replacement is
the sole narrow exception to the rule that attachment does not grant Electron
permission to stop an external daemon, and it may interrupt active runtimes.

Explicit application quit first closes lifecycle reconciliation admission,
joins any recovery already in progress, and then revokes Electron's UI
sessions. Electron may request authenticated graceful shutdown only for the
daemon it currently owns. The default application policy stops an owned daemon
after bounded drain, while the keep-running setting leaves it alive. External
daemons, Electron crashes, and forced application exits leave the daemon and
agent runtimes running. No path uses an unauthenticated process kill as a
shutdown fallback.
Electron update installation uses the same authenticated owner-safe shutdown
and stops an owned daemon even when keep-running is enabled. If the attached
daemon is externally owned, installation is deferred rather than stopping it.

On Windows, an Electron-owned daemon that already holds the current instance
lock may reconcile the root `\AgentTeam` Scheduled Task left by older clients before
starting any server connection. Reconciliation is allowed only when the task
action targets the canonical managed AgentTeam binary, uses the historical daemon
arguments, and remains active beyond the bounded startup period in which a
current lock-aware service must yield the lock. The daemon must verify process
exit before deleting that legacy registration. A current service registration
that yields the lock remains installed, and any ambiguous or failed cleanup
aborts Electron-owned daemon startup rather than allowing two machine
connections.

## Contract Surfaces

The first-party daemon contract covers:

- daemon control-plane WebSocket;
- machine connect, reconnect, disconnect, and version reporting;
- agent start, stop, sleep, wake, interrupt, and workspace reset commands;
- workspace browse, read, seed-file apply, delete, and machine scan/delete commands;
- local runtime session ownership, resume decisions, running set, activity, trajectory, recovery points, and context recovery;
- runtime permission mode enforcement and runtime policy freshness;
- direct AgentTeam MaaS runtime bridging when the daemon advertises the required capabilities;
- local operational/API state surfaces and selected-server gateway;
- daemon receipt, owner/control authentication, renderer UI-session lifecycle,
  orphaned Electron-owner adoption, and graceful shutdown;
- Electron-hosted server selection, user login/registration, Workspace activation/restoration, and automatic current-computer creation when the current user has no Machines;
- runtime outbound tools exposed through MCP and hidden agent tools CLI;
- knowledge retrieval proxying and Exec-backed provider/app actions through server-side authorization.

## Runtime State Ownership

Runtime state has two classes:

- **Durable intent and identity**: workspace membership, agent profile, machine binding, runtime selection, model settings, auto-wake preference, authorization, and other server-owned product facts.
- **Live runtime state**: daemon connection presence, running/starting agents, current runtime session IDs, activity/status text, trajectory, and recovery details reported by the current daemon connection.

The daemon is the source of truth for live runtime state while it is connected. Server read models may keep an in-memory projection of the current daemon WebSocket state and overlay that projection onto already-authorized durable result sets. Server code must not persist live runtime telemetry to TiDB as current truth.

```mermaid
flowchart TD
    daemon[daemon process]
    ws[authenticated daemon WebSocket]
    hub[server daemon hub\nin-memory projection]
    durable[durable store query\nauthorization + identity]
    dto[bounded browser / machine / MCP DTO]

    daemon -->|ready, runningAgents, agent:status, agent:activity, agent:session| ws
    ws --> hub
    durable --> dto
    hub -->|overlay only rows returned by durable query| dto
```

Overlay rules:

1. Durable store queries decide which agents/machines the caller may see.
2. The daemon hub may overlay machine online/offline, agent active/inactive, live session ID, and live activity/status onto rows already returned by durable queries.
3. The overlay must not add agents that the durable query did not return.
4. The overlay must not widen list/query scope or add browser/MCP polling.
5. Synthesized current-instance DTOs may be built from in-memory live session IDs for display, but must not be written to `agent_instances`.
6. When a daemon disconnects or is replaced, its connection-scoped runtime projection is discarded. Reads show offline/unknown live state until the daemon reconnects and replays current state.

Compatibility fields such as persisted session mirrors may exist only as explicitly documented adapter state for daemon versions that do not support local session resume. They must not become the source of truth for current runtime status, activity, current instance, or machine online/offline.

## Runtime Diagnostics

Runtime diagnostics are local operational evidence, not user-facing activity.
When OpenCode runtime diagnostics are enabled, Desktop writes bounded,
permission-restricted, scrubbed JSONL beside the bounded `opencode serve` log
under the agent workdir's `diagnostics/` directory. High-frequency protocol
breadcrumbs must not be emitted as `agent:activity` or `agent:trajectory`.
Diagnostic records and raw serve-log lines carry a per-process launch ID so a
primary runtime and detached recovery process can be distinguished without
recording prompt, message, or tool-input bodies.

AgentTeam Desktop may export this evidence as a local diagnostic archive from the
current Machine settings. The archive is scoped through the authenticated UI
session to the selected Workspace, the current Desktop Machine binding, and
only the agents owned by that binding. It contains a fixed, bounded set of
scrubbed daemon and OpenCode diagnostic logs plus a path-free manifest; it must
not scan arbitrary paths or include configuration credentials, environment
variables, workspace files, Memory, Knowledge Bases, prompts, messages, tool
inputs, runtime databases, or resume state. Collection saves the archive
locally and never uploads it without a separate explicit user action.

Headless runtime permissions must be decided non-interactively from the
immutable launch policy. An unexpected permission request must either be
resolved by the same policy or fail the runtime; it must never leave the agent
permanently blocked.

The Session Link treats activity and trajectory as shed-able live telemetry
under backpressure. Its outbound queue must retain headroom for wake status,
runtime failure, and recovery-point evidence. Abnormal carrier termination logs
the failing pump and WebSocket close code when available; bounded drop summaries
record message type, queue occupancy, reason, and cumulative count rather than
emitting one warning per dropped telemetry item.

Detached OpenCode recovery-point GC reuses one temporary OpenCode server for the
whole command batch. It must not start one temporary server per artifact because
that multiplies process and local session-store contention while an agent may
already be running.

## Session And Resume

`agent:start` sends durable configuration and user intent. Daemons that advertise `agent:local-session-resume` decide resume versus fresh start from daemon-local state without server DB session lookup.

A true runtime resume must restore an interrupted durable turn without replaying
a terminal turn. Ordinary provider-session resume is delegated to runtime
authority after the two compatibility guards; daemon-local transcript/artifact
probes must not reject a session (Claude transcript checks remain only for the
explicit rewind/resume-at recovery boundary). A fresh-session cutover is
limited to runtimes with a typed, pre-input authority boundary: Codex
app-server RPC, OpenCode HTTP 404/410, Kimi ACP session/resume, and Pi RPC
get_state. Claude's ordinary `--resume` path has no deterministic pre-input
invalid-session response, so it remains fail-closed and must not infer a fresh
cutover from stderr or replay an already-written prompt. All transport,
timeout, cancellation, and input-outcome ambiguity remains fail-closed.

A server-initiated session reset is explicit user intent. When the server sends `agent:stop` with `clearSession=true`, daemons with local session resume support must clear daemon-local session resume metadata without deleting ordinary workspace files.

Runtime policy is authoritative current configuration, not resume state. Server stores the runtime policy generation and sends a runtime policy snapshot in `agent:start.config.runtimePolicy` and machine catalogs. Daemons must build launch permissions from the current runtime policy and agent config only. Cross-daemon provider-session reuse has exactly two guards: the current `RuntimePolicySnapshot` and the semantic fingerprint of `baseInstructions` + `developerInstructions`. Stored runtime or permission copies must never override current policy. A policy mismatch or missing secure policy state must fail closed by clearing security-sensitive resume metadata and fresh-starting under the current policy. Daemon URL/port/proxy, MCP endpoint/command/args/CWD/tool surface, and provider wiring are not resume guards. `SessionResumeSafe`/epoch/version remain delivery ownership and reconciliation fences; an unsafe value must not clear the provider thread or authorize blind replay.

## Permission Modes

`full_access` is the supported user-facing agent runtime permission mode.
Historical permission-mode values are compatibility aliases normalized to
`full_access`; they do not select a workspace-only sandbox. Internal restricted
or isolated Agent executions use launch-scoped tool controls rather than a
second permission mode. The retired `knowledge_qa_scratch` mode must fail
closed; server-hosted Knowledge Q&A does not create or launch a daemon Agent.

## MaaS Runtime Bridge

Direct `agentteam-maas` runtime bridging is capability-gated.

- A daemon that can start the local MaaS loopback bridge advertises `agent:maas-direct` and may receive `credentialKind=maas_capability` config.
- A daemon that owns and replays AgentTeam runtime session authority for MaaS capability claims advertises `agent:runtime-session-authority` and follows [MaaS Runtime Session Authority](../maas/runtime-session-authority.md).
- Daemons without direct MaaS support receive the server-projected proxy-key path.

The local MaaS bridge preserves streaming delivery semantics: it flushes model
output as it arrives and aborts the local response when reading the upstream
body fails after response delivery has started. It must not turn an upstream
stream failure into a clean EOF or append a replacement JSON error to a partial
stream.

Runtime control inputs such as the final visible-reply check must use a
runtime's structured delivery capability when available. They must not fall
back to an unsupported stdin encoder that cannot report whether OpenCode or
another protocol runtime accepted the input.

Runtime session authority is distinct from provider resume/session identifiers. Only Codex, Kimi, and OpenCode have deterministic typed boundaries that may perform one fresh-session fallback and emit one structured runtime diagnostic; Claude ordinary resume and Pi's pinned RPC protocol have no deterministic invalid-session authority, so their failures remain fail-closed. Codex and Kimi must first accept the protocol's allowed `-32000` server-error boundary, then inspect only direct `code`/`kind`/`type` values (`thread_not_found`/`invalid_thread_id` or `session_not_found`/`invalid_session_id`); `reason`, `status`, `error`, `message`, bare `invalid`, and bare `not_found` are never session authority. Their explicit thread/session message compatibility forms are bounded to that same outer code. Pi's pre-prompt `get_state` call is only a read-only consistency check: the pinned response has success=true plus `RpcSessionState` on success and success=false plus error on failure, with no typed invalid-session token. Missing, malformed, inconsistent, transport, and ordinary provider/configuration responses therefore never trigger a fresh retry.

## Managed And External Runtimes

AgentTeam manages the `builtin` runtime by shipping an OpenCode executable with the
first-party artifact. On macOS and Windows it is part of the whole Electron
application; on Linux it is part of the headless daemon/CLI release payload.
Starting `builtin` uses the managed OpenCode binary through the existing
OpenCode HTTP/SSE driver. Missing, non-executable, or version-incompatible
managed OpenCode means the installation is incomplete.
Builtin OpenCode data, state, and cache are persistent but isolated per agent
under `~/.agentteam/agents/<agent-id>/.runtime/opencode/`; they must not reuse the
native user OpenCode database. This root is independent of launch workdirs.
Builtin launch removes `OPENCODE_DB` and `OPENCODE_TEST_HOME`, then
case-insensitively replaces managed environment aliases before spawning.
External `opencode` keeps the user's native storage semantics.

External `opencode`, `claude`, `codex`, `kimi`, and `pi` runtimes may be detected, configured, and used. Desktop does not own their installation or upgrade lifecycle. External runtime state should report operating conditions such as detected, usable, unsupported capability/version, not found, or manual configuration required. Codex startup requires the `app-server` capability; Kimi startup requires the public Agent Client Protocol entrypoint (`kimi acp`); Pi startup requires the RPC entrypoint (`pi --mode rpc`). AgentTeam injects its configured provider into the launched Kimi and Pi processes rather than invoking their interactive login flows.

On macOS and Windows, Electron's updater replaces the native host, renderer, Go
sidecar, and managed `builtin` runtime as one signed application version. The Go
updater is not a Desktop application updater on those platforms. It remains the
upgrade path for Linux/headless installations.

## Owner-Private Execution Binding

When the server delivers an owner-scoped private-agent wake, the daemon exposes the parent channel/thread/task target to the runtime and carries owner-private scope metadata in authenticated session/current-delivery state. Agent-facing prompts and tool schemas must not expose hidden execution IDs or private-context target aliases.

## Design Requirements

Daemon-facing changes must preserve these requirements:

1. The default user path is the first-party `agentteam daemon` command.
2. Runtime behavior, workdirs, recovery, continuation, and outbound tools follow AgentTeam product semantics.
3. Server-side durable stores own identity, authorization, and product intent; daemon-local state owns current runtime execution state.
4. Live runtime read models use bounded overlays over authorized durable rows, not telemetry persistence.
5. Capability-gated features fail closed when the daemon or driver cannot enforce the requested contract.
6. The renderer receives only a scoped UI session; daemon receipt/control
   capability and process ownership remain outside the renderer.
7. Long-lived specs describe current facts and stable rules; implementation history belongs in Git history.

## Out Of Scope

This spec does not require:

- parity with external daemon implementations;
- automatic behavior changes caused only by external runtime product changes;
- one-click installation or package-manager management for external OpenCode, Claude Code, Codex, or Kimi Code;
- storing live runtime presence, activity, session, or trajectory as server durable truth;
- exposing private execution identifiers or daemon-local leases as general agent-facing API fields.

## Related Specs

- Desktop architecture: [AgentTeam Desktop Architecture](./desktop-architecture.md)
- Runtime outbound tools: [Runtime Outbound Tools](./runtime-outbound-tools.md)
- Token 诊断契约：[AgentTeam Token 消耗诊断](./token-optimization.md)
- Turn-target 回复上下文：[Turn-Target 回复上下文](./turn-target-reply-routing.md)
- Daemon transport/session link: [Daemon Transport](./daemon-transport.md)
- Runtime prompt and delivery model: [Prompt Compiler Architecture](./prompt-compiler-architecture.md)
- Visible reply/wake behavior: [Wake Visible Reply Contract](./wake-visible-reply-contract.md)
- Version track contract: [Versioning](../versioning/versioning.md)
- MaaS runtime authority: [MaaS Runtime Session Authority](../maas/runtime-session-authority.md)
