# NowWork Daemon

The daemon is NowWork's local execution adapter. It holds a machine credential, keeps one control-plane
WebSocket open, and launches coding runtimes on that machine. Product policy remains on the server.

## 已完成搬迁后的存储运行

NAS / `NOWWORK_HOME` 搬迁和旧 daemon 布局标准化入口已退役。daemon 不再提供内部
`storage-migration-worker` / `storage-migration-recover` 命令，不处理 `daemon:migrate`、
`daemon:storage-doctor`、`daemon:storage-migrate` 或迁移确认消息，也不再声明
`storage_migration_v1` / `storage_migration_status_v1` / `daemon_layout_migration_v1` 能力。

现有 `installations.json` 即使仍有历史 `storageMigrationId`，也按当前 installation 解析
Agent、仓库和报告目录；启动、hello 和身份绑定不再读取迁移 receipt、outbox 或备份。
hello 继续报告布局、环境、Workspace、路径指纹与 ownership，但不再发送迁移 ID。
每次连接仍须完成 Server 身份绑定后才能执行任务；Workspace 或 Machine 身份不匹配时拒绝执行。

旧布局读取兼容、日常安装/升级、NAS 所有权校验、执行 journal 与 ACK/replay 继续保留。
退役代码不会删除历史安装字段、迁移记录、receipt、备份或旧数据目录；这些数据的保留与清理另行处理。

## Run

For a temporary foreground connection, run:

```bash
npx @nowcrew/daemon@latest --server-url https://nowwork.example --api-key sk_machine_...
```

The package has two release channels. The `in` environment consumes stable `x.y.z` releases through
the `latest` tag. The `dev` environment prefers `x.y.z-beta.n` prereleases through the `beta` tag,
and also accepts stable `x.y.z` releases for compatibility and cross-channel updates.
The parser also accepts the historical `x.y.z-beta-n` spelling and normalizes it for ordering; new
published versions should use the dotted SemVer form.
Use `beta` by default for dev installations; an explicit `latest` target is also supported there.
Never use `beta` for an in installation. See the
[Daemon Release Runbook](../docs/releases/daemon-release-runbook.md) for release and rollback procedures.

This compatibility form stays resident in the current terminal and reconnects with backoff. New
**Add Computer** commands use the managed automatic installation described below. For local development, run
`pnpm --filter @nowcrew/daemon daemon`. `crew-daemon run --agent <handle> --channel <id>` is the legacy
manual one-shot entry.

## Browser-Approved Computer Setup

The Web UI generates a token-free command for a specific Workspace and Machine:

```bash
npx -y --registry https://registry.npmjs.org --package @nowcrew/daemon@latest nowwork-computer setup /nowcoder-app --machine <machine-uuid> --server-url https://nowwork.nowcoder.com
```

`nowwork-computer setup` opens a one-time authorization page, prints the user code, polls until an owner or
admin approves it, then installs the selected daemon through the existing isolated profile and native service
installer. The command contains no long-lived `sk_machine_*` credential. The issued credential is held
only in memory until the private profile is written, and becomes permanent after successful finalization.
Each command run needs a new approval. Browser approval expires after ten minutes; credential exchange starts
a separate thirty-minute installation window. Failed installations restore the prior credential when still owned
by that setup. Interrupted setups are reconciled at server startup and every minute.
Only one unfinished credential exchange can own a Machine at a time; a concurrent setup receives a conflict.
Use `--no-browser` on hosts without a desktop browser and
`--json` for machine-readable status output. `--server-url` must be explicit for remote servers; it may also
come from `CREW_SERVER_URL`.

Alternatively, install both command entries first, then run setup:

```bash
npm install --global --registry https://registry.npmjs.org @nowcrew/daemon@latest
nowwork-computer setup --server-url https://nowwork.nowcoder.com /nowcoder-app --machine <machine-uuid>
```

With `CREW_SERVER_URL=https://nowwork.nowcoder.com` already set, the second command can be shortened to
`nowwork-computer setup /nowcoder-app --machine <machine-uuid>`. Replace the placeholder with the
Machine UUID from the Web command. New production setup links use `nowwork.nowcoder.com`;
the old `nowwork-in.nowcoder.com` hostname remains a production alias for existing profiles and pairing.

The setup flow validates the exact Workspace and Machine UUID and waits for the machine control-plane hello
to confirm Workspace, Machine, environment, profile, and storage identity. It does not migrate NAS or copy
existing `.crew` data. Existing `crew-daemon install --server-url ... --api-key ...` and profile lifecycle
commands remain available as compatibility and recovery entry points.

## Machine Environment

The daemon loads exported variables from its OS account’s bash/zsh configuration on macOS/Linux,
and native system/user variables on Windows. Agent directory layers override ordinary machine values;
running tasks keep immutable snapshots. See [machine environment](../docs/machine-environment.md) for
source rules, refresh behavior, local controls and the Windows native acceptance status.

## Computer Profiles And Service Lifecycle

Persist a named connection without putting its machine token in shell history or service arguments:

```bash
printf '%s' "$CREW_MACHINE_TOKEN" | crew-daemon profile save work \
  --server-url https://nowwork.example --release-tag latest --token-stdin
crew-daemon profile list
crew-daemon profile show work        # reports configured=true; never prints the token
crew-daemon serve --profile work
```

Profile files live under `~/.crew/daemon/profiles`. Unix writes use mode `0600`; Windows tokens are
encrypted with CurrentUser DPAPI and the profile ACL is restricted to the current user. Windows refuses
legacy plaintext profiles and fails the save if DPAPI or ACL hardening is unavailable.

The connection command generated by **Add Computer** installs the package into an isolated stable prefix,
saves the private profile, installs the native user service, starts it, and verifies that it is running:

```bash
npx -y --registry https://registry.npmjs.org @nowcrew/daemon@beta install \
  --server-url https://nowwork-dev.nowcoder.com --api-key sk_machine_... \
  --release-tag beta --registry https://registry.npmjs.org
```

The installer selects the fixed production layout for both `nowwork.nowcoder.com` (canonical) and
`nowwork-in.nowcoder.com` (legacy compatibility), and the fixed `dev` layout for
`nowwork-dev.nowcoder.com`. It compares the parsed hostname exactly; lookalike suffixes are not accepted.
An unknown hostname is rejected before any write and reports how to retry with an explicit custom profile:

```bash
npx -y --registry https://registry.npmjs.org @nowcrew/daemon@beta install \
  --server-url https://lab.example.com --api-key sk_machine_... --profile lab --release-tag beta
```

Custom profiles use `~/.crew/daemon-<profile>`, `~/.crew/agents-<profile>`, and
`~/.crew/daemon-runtimes/<profile>`. The names `in` and `dev` remain reserved for their known domains.
Re-running the command updates the exact isolated package, rotates the saved machine credential, repairs the
exact selected service, and verifies it again. An exact generated pre-namespace service is migrated only for
the selected profile; a drifted, duplicate, wrong-user, or otherwise unverifiable service fails closed.

The lower-level lifecycle commands remain available for diagnostics and manual operation:

```bash
npm install --global @nowcrew/daemon@latest
crew-daemon install --profile work
crew-daemon start --profile work
crew-daemon status --profile work
crew-daemon doctor --profile work
crew-daemon restart --profile work
crew-daemon stop --profile work
crew-daemon uninstall --profile work
crew-daemon profile remove work
```

`doctor` reports the native service, Agent root and execution journal plus managed-update diagnostics for
the host registry, exact service identity, and installation lease. It is read-only: stale or corrupt lock
state is reported but never acquired, repaired, or removed. Use these checks when the Web update action is
hidden.

Descriptor-only `install --profile` is intentionally refused from an `npx`/temporary cache entry because
the service must keep a stable executable path across cache cleanup and upgrades. The automatic
`install --server-url` mode is allowed from npx because it first installs the invoking package's exact
version under the profile-specific stable prefix and points the service at that entry.

macOS uses a user LaunchAgent, Linux uses a systemd user unit, and Windows uses Task Scheduler at logon.
On macOS `install` writes the plist, `start` bootstraps it, and `stop` boots it out, so `KeepAlive` cannot
silently resurrect a stopped daemon. Always use `status` as the source of truth instead of assuming that
an install or start request proves the process is running.
Profiles also capture the install-time runtime `PATH`, so user-installed CLIs remain discoverable under
minimal service-manager environments. Service descriptions contain only `serve --profile work` and the
non-secret profile directory; the token and PATH are read by the daemon at runtime and never appear in service arguments. Lifecycle commands fail
when the native manager rejects an operation. Windows status uses the
locale-independent `Get-ScheduledTask.State` enum and reports healthy only for `Running`. Its scheduled
task has no execution time limit, runs on battery, and restarts after failures.

Upgrade the global daemon package, optionally restarting one installed profile afterward:

```bash
crew-daemon upgrade
crew-daemon upgrade --profile work
crew-daemon upgrade --profile work --release-tag beta
```

With `--profile`, upgrade validates isolation, stops the native service, acquires the installation lease,
replaces only the entrypoint-derived npm prefix, releases the lease, and starts the service. If package
replacement fails, it still attempts to start the previous installation. Without `--profile`, upgrade
refuses to replace a prefix currently leased by a running daemon; it also permits only the currently
installed daemon's release channel. Cross-channel upgrades (for example installing `beta` from a stable
global package) require `--profile`, so the target server environment and profile isolation are verified.
Use the profile form for any managed service.

## Managed Self-Update

An owner or admin can update an eligible outdated daemon from the computer detail view. The HTTP request
returns immediately; the server records the exact registry release, dispatches it to the daemon, and the
Web UI polls the durable `pending -> installing -> restarting -> completed` state. Completion is recorded
only after the native service reconnects and reports that exact version.

Existing installations need one manual bootstrap to a release containing managed self-update:

```bash
# Default daemon home (~/.crew/daemon): upgrade, adopt the exact live descriptor, then reconnect.
crew-daemon upgrade --profile work
crew-daemon install --profile work
crew-daemon restart --profile work
crew-daemon doctor --profile work
crew-daemon status --profile work
```

For a non-default daemon home, the daemon now recognizes the pre-registry service ID when its descriptor
exactly matches the running profile's node path, package entry, profile name, and `--daemon-home`. No
uninstall/reinstall migration is required. The first self-update uses that exact legacy service ID for the
restart; future service lifecycle commands can continue to migrate it explicitly if desired:

```bash
CREW_DAEMON_HOME="$HOME/.crew/daemon-dev" crew-daemon doctor --profile dev
CREW_DAEMON_HOME="$HOME/.crew/daemon-dev" crew-daemon status --profile dev
```

If the target descriptor has drifted or the profile is stopped, the daemon fails closed and remains
manually upgradeable. On macOS, another unregistered legacy LaunchAgent may coexist only when its plist
exactly matches a generated NowCrew service and resolves to a different npm prefix. An unverifiable or
same-prefix descriptor still fails closed.

The daemon advertises `daemon_update_v1` only when all of these conditions hold:

| Requirement | Supported shape |
| --- | --- |
| Platform | macOS LaunchAgent or Linux systemd user service |
| Entrypoint | global `@nowcrew/daemon/dist/main.js`, not npx or source/tsx |
| Startup | `crew-daemon serve --profile <name>` through the installed service |
| Registry | registered descriptors remain conflict-free; the exact target legacy descriptor may be adopted once; an exact macOS legacy descriptor on another npm prefix may coexist |
| Identity | service ID, descriptor, daemon home, Agent root, entrypoint, package root, and npm prefix match |
| Service | the selected profile is running and holds the installation lease |
| Install root | a standard writable Unix global npm prefix can be derived from the running entrypoint |

Foreground npx/source runs, stopped or uninstalled services, legacy unregistered descriptors, shared roots,
and read-only global package roots remain manually upgradeable. These shapes do not show an update button.
Windows Scheduled Tasks are eligible only when the task action, current-user principal, running instance,
process identity, isolated npm prefix, and installation lease all match exactly.
For the default daemon home, `install` adopts only an exact legacy descriptor; the following restart makes
the daemon reconnect and advertise the newly registered capability. A pre-namespace service in a non-default
daemon home must be migrated with `uninstall` followed by `install`, because the new service ID includes the
daemon-home fingerprint. A host that runs multiple environments can keep managed updates by isolating all
three roots:

| Environment | Agents root | Daemon home | npm prefix |
| --- | --- | --- | --- |
| in | `~/.crew/agents` | `~/.crew/daemon` | `~/.crew/daemon-runtimes/in` |
| dev | `~/.crew/agents-dev` | `~/.crew/daemon-dev` | `~/.crew/daemon-runtimes/dev` |

The host execution coordinator intentionally remains shared under `~/.crew/daemon/compute-execution`, so an
update in either environment still refuses to run while any profile on the machine is executing Agent work.
Managed service ownership and installation leases are shared under `~/.crew/daemon/managed-services`.
Registry writes are private, atomic, schema-validated, and serialized across processes. Each non-default
daemon home also contributes a stable hash to the native service ID, so equal profile names can coexist.

Before replacing files, the daemon refuses new work, requires zero local active or queued work, and
non-blockingly acquires every host execution slot. It then installs only the server-selected exact
exact `x.y.z` or `x.y.z-beta.n` version with npm using the prefix derived from the running global entrypoint, verifies the installed
package version, and schedules the existing native service restart from a detached helper. macOS uses
`launchctl kickstart -k` to avoid the `bootout`/`bootstrap` race; Linux uses `systemctl --user --no-block
restart`. The helper reports manager failure over IPC. A failed handoff releases both execution barriers and
reports `restart_failed`; a SIGTERM/shutdown initiated by a successful handoff releases them during orderly
shutdown. No package name, registry, command, or free-form argument comes from the browser control message.

The release guard packs with pnpm so workspace dependencies become registry-compatible version ranges,
rejects any runtime dependency that still uses `workspace:`, and installs the tarball into a clean npm
prefix before a daemon version can be published.

Failures are recoverable and keep a bounded code in the machine record:

| Code | Operator action |
| --- | --- |
| `runtime_busy` | let active/queued Agent work finish, then retry |
| `dispatch_unavailable` | restore the daemon connection, then retry |
| `ineligible` | re-run `status` and the eligibility checks above; upgrade manually if needed |
| `install_failed` | fix npm/network/write access; the old process keeps serving, then retry |
| `version_mismatch` | inspect the global npm installation and install the intended version manually |
| `restart_failed` | run `crew-daemon restart --profile work`; the package may already be updated |
| `update_in_progress` | wait for the active request to reach a terminal state |
| `update_timeout` | inspect `crew-daemon status --profile work`, restart if needed, then retry |

The Web UI supports owner/admin rollback to an exact release in the configured channel. When the managed
control plane is unavailable, install a known exact release and restart the service manually:

```bash
npm install --global --prefix <environment-prefix> --ignore-scripts --no-audit --no-fund @nowcrew/daemon@<previous-release>
crew-daemon restart --profile work
crew-daemon status --profile work
```

Use the `npmPrefix` reported by `crew-daemon doctor --profile work`; do not rely on the interactive shell's
current global npm configuration on a multi-environment host.

## Execution Boundary

The server selects the machine and sends a validated `execution:start` containing:

- fully rendered system and wake prompts;
- runtime/model/reasoning and timeout;
- requested permission and channel/thread identifiers;
- reporting flags for final text, activity, and console streams.

An Agent bound to a computer has a hard machine constraint: if that computer is offline or incompatible,
dispatch reports unavailable instead of running on another compatible daemon. An unbound Agent may use
the global compatible-machine pool.

The daemon intersects requested permission with local policy, checks advertised resource limits, prepares
the local workspace/environment, and launches only a built-in runtime adapter (`claude`, `codex`, `kimi`,
`hermes`, `opencode`, or `deepseek-harness`). It does not decide collaboration rules, scheduled output policy, fallback delivery, or thread
behavior. Those are server responsibilities.

The stable adapters keep prompts out of provider argv wherever the provider protocol allows it:

| Runtime | Transport | System/wake input | Native resume |
| --- | --- | --- | --- |
| Claude | stream-json CLI | daemon-owned system prompt file + user input | CLI session id; thread-bound: matching native SessionStart proof + SDK initialization |
| Codex | app-server JSON-RPC over stdio | `developerInstructions` + turn input | `thread/resume`; thread-bound: returned id must match |
| Kimi | ACP over stdio | `session/prompt` | unbound: `session/resume` (`session/load` fallback); thread-bound: cold |
| Hermes | ACP over stdio | `session/prompt` | unbound: `session/resume` (`session/load` fallback); thread-bound: cold |
| OpenCode | `run --format json` | stdin | unbound: `--session`; thread-bound: cold |
| DeepSeek Harness | ACP over stdio | `session/prompt` | none (fresh session per execution) |

Kimi ACP currently requires a Kimi account even when the CLI has a working custom provider. Only for
the explicit `Authentication required` response, the adapter retries once through Kimi's stream-json
CLI; only unbound executions can carry a saved native session id. Unrelated ACP failures stay failures. The CLI fallback is
subject to the Windows UTF-16 argv guard, while protocol-v1 itself remains disabled on Windows until
durable Job Object ownership is available.

Hermes uses only ACP capabilities advertised by the installed CLI. Model and effort choices come from
a short-lived discovery session; credentials, provider profiles, plugins, and `HERMES_HOME` remain
machine-local. For unbound executions, a missing resumed ACP session is retried once as a fresh session.

OpenCode receives the prompt through stdin and runs with both cwd and `PWD` anchored to the Agent
workspace. Models and variants come from `opencode models --verbose` (with the plain catalog as a
compatibility fallback). For unbound executions, a missing resumed session is retried once without `--session`; malformed,
empty, incomplete, or explicitly failed JSON streams remain failures. NowWork never writes
`opencode.json` or replaces `OPENCODE_CONFIG_CONTENT`. The daemon advertises OpenCode as executable
only when `opencode run --help` exposes `--format`, `--dangerously-skip-permissions`, `--dir`,
`--model`, `--variant`, and `--session`; older installations remain visible as detected CLIs until
they are upgraded, rather than falling back to a permission override that user config could weaken.

Install the pinned DeepSeek Harness ACP server on the daemon machine:

```bash
npm install --global @deepseek-ai/dsh-acp-demo@0.0.1-rc.1
command -v dsh-acp-demo
```

The executable is only the ACP app loader; it does not embed a runnable `cordis.yml`. Install or build
the plugins referenced by your DeepSeek Harness composition, then set both `DEEPSEEK_API_KEY` and an
absolute `NOWCREW_DEEPSEEK_HARNESS_CONFIG=/path/to/cordis.yml` in the daemon service environment. The
official repository's `examples/acp-agent/cordis.yml` is the reference composition. A missing or relative
config path keeps the runtime out of `executionRuntimes`.

`dsh-acp-demo --config <absolute-path>` is launched directly with ACP JSON-RPC on stdio; do not append
an `acp` subcommand and do not substitute `dsh --profile headless`.
Provider credentials and model selection remain machine-local. The daemon sets `DSH_PERMISSION_MODE`
from the effective NowWork permission: `sandboxed` -> `read-only`, `workspace_write` ->
`workspace-write`, and `full_access` -> `danger-full-access`. Outside full access, an ACP permission
request is rejected once when that option is unambiguous, otherwise it is cancelled. The current adapter
uses fresh sessions and committed text; it does not claim resume, images, MCP, reasoning, tool-progress,
or transcript support. Agent environment variables cannot override `DEEPSEEK_*`, `DSH_*`, or
`NOWCREW_DEEPSEEK_HARNESS_CONFIG`; those values always come from the daemon service environment.

Unbound Codex, Kimi, Hermes, and OpenCode sessions retain the same configured warm/budget policy and keyed
lease as Claude through protocol v1. Thread-bound Claude/Codex sessions keep their native session across
warm-cache expiry, token estimates, and turn counts; verified restoration receives only accepted-cursor
deltas. New sessions and runtimes without verified restoration receive complete current-thread history
before the business turn. See the [thread context contract](../docs/thread-scoped-native-context.md).
A first-progress watchdog covers a provider that accepts a turn but remains
semantically silent; after the first semantic event, the execution's configured total timeout remains
authoritative so a legitimate long-running tool is not killed for quiet output.
DeepSeek Harness is execution-v1-only and intentionally skips native session persistence.

Protocol support and limits are advertised in `machine:hello`. `runtimes` reports every recognized CLI
found on `PATH`; `executionRuntimes` separately reports the installed CLIs backed by a complete built-in
adapter. The server must use the latter for admission and treats the former as diagnostic inventory only.
The daemon sends a conservative first hello without waiting for third-party handshakes, then refreshes it
after the optional Kimi/Hermes/OpenCode/DeepSeek Harness probes finish. Work targeting those optional runtimes waits for the
same full probe result; reconnects share one in-flight probe, and shutdown aborts any unfinished probe and
its child process.
Old daemons that omit `executionRuntimes` are conservatively interpreted as the intersection of installed
CLIs and server-supported adapters. Unknown required protocol semantics are rejected before side effects.
Protocol-0 `agent:start` remains only for the server-governed compatibility window.

## Project Skills

On macOS and Linux, an owner or admin can register multiple local project repositories from a Computer
profile and bind selected Skills to individual Agents. The daemon keeps the only durable copy of each
absolute path in:

```text
<CREW_AGENTS_ROOT>/.crew/projects.json
```

Each project is scanned at `<project>/.agents/skills/*/SKILL.md`. The server stores only a bounded,
path-free inventory and logical `(projectId, skillName)` bindings. The local path crosses the server only
while an add command is forwarded to the selected online Computer; it is not written to the workspace
database, structured logs, inventory frames, or responses. Project Skills are disabled on Windows.

One binding set is projected into both Runtime discovery layouts:

```text
<agentsRoot>/<handle>/.agents/skills/<name>                         # Codex symlink
<agentsRoot>/<handle>/.crew/claude-skills/.claude/skills/<name>    # Claude symlink
```

The two complete directories switch under one per-Agent reconcile/launch lock. A failed switch restores
the prior complete projection; a missing project or Skill removes only that link and does not block other
work. These are ordinary writable symlinks, not snapshots or a sandbox: an Agent running as the daemon
user can modify the source Skill through them. That write-through risk is intentionally accepted.

Codex protocol-v1 task directories are opaque
`<agentsRoot>/<handle>/tasks/<safe-prefix>-<sha256>` paths. The daemon writes `.nowwork-root` at the Agent
root and adds it to Codex `project_root_markers`, so upward discovery reaches `.agents/skills` without
changing the task cwd. Claude receives
`--add-dir <agentsRoot>/<handle>/.crew/claude-skills`; Claude treats those Skills as `projectSettings`,
so a machine policy that disables project settings also disables this source even when the projection is
healthy.

Ordinary Codex always uses the shared user Home: the inherited `CODEX_HOME`, or `~/.codex` when
unset. There is no Computer-level Home mode or private-Home migration command. Existing user-level
discovery is unchanged: Claude may still load `~/.claude/skills`, and Codex may load `$CODEX_HOME/skills`
and `~/.agents/skills`. The DeepSeek Codex variant keeps its Agent-private `.codex-deepseek` Home for
provider credentials. Project bindings do not hide or rewrite any of those sources.

Binding changes reconcile immediately when the Computer is online. On reconnect the server sends the
complete binding set for every Agent assigned to that Computer, including empty sets that clear stale
links left by offline unbinds. Every non-empty execution snapshot is also reconciled immediately before
Runtime launch, which recovers from a missed notification or daemon restart. Skill file content changes
are visible through the symlink immediately; adding, deleting, renaming, or changing frontmatter requires
a rescan. Web descriptions remain at the last inventory value until that rescan.

## 目录投影测试的平台范围

`test/directory-projection.test.ts` 在所有平台运行，覆盖路径检查、复制发布、只读权限请求及失败恢复。
其受控文件系统夹具记录目录 `chmod` 请求，但保留宿主目录的写权限，避免把 macOS 的目录重命名规则当作 Windows 行为。
生产代码的只读保护不受此夹具影响。

`test/directory-projection.windows.test.ts` 仅在 Windows 上运行，使用真实文件系统验证只读目录发布与文件保护。
非 Windows 的全量测试会明确跳过该文件，不能据此宣称 Windows 原生行为已验证。

## Reliability

Protocol-v1 lifecycle is `accepted → started → completed`, followed by a server
`execution:completion-ack`. Reconnect uses `execution:sync`; cancellation uses `execution:cancel`.
Activity and console frames are best-effort, while lifecycle and terminal effects are durable on the
server.

Before accepting work, the daemon writes a local journal entry under:

```text
<CREW_AGENTS_ROOT>/.crew/executions/<executionId>.json
```

`CREW_AGENTS_ROOT` defaults to `~/.crew/agents`. On restart, accepted entries become interrupted failures;
running supervisors are identity-checked and terminated before interruption is reported. A lock prevents
two daemon processes from sharing one journal.

Protocol v1 is advertised only when the platform has a durable process-tree backend. POSIX uses an
owned process group. Native Windows intentionally fails admission until a Job Object backend has passed
crash-cleanup tests; `taskkill /T` is not treated as equivalent ownership.

## Local Policy

Useful environment controls:

```text
CREW_RUNTIME_SAFE=1
CREW_EXECUTION_MAX_PROMPT_BYTES=256000
CREW_EXECUTION_MAX_TIMEOUT_MS=10800000
CREW_EXECUTION_MAX_EVENT_BYTES=64000
CREW_MAX_PARALLEL=10
CREW_SCHEDULED_MAX_PARALLEL=4
CREW_EXECUTION_MAX_QUEUED_PER_AGENT=32
CREW_EXECUTION_MAX_PARALLEL_TOTAL=10
CREW_EXECUTION_MAX_QUEUED_TOTAL=128
CREW_EXECUTION_MAX_STARTING_TOTAL=10
CREW_EXECUTION_MAX_STARTING_PER_RUNTIME=10
CREW_EXECUTION_START_GAP_MS=500
CREW_EXECUTION_STARTUP_TIMEOUT_MS=120000
# Retained for compatibility; accepted executions wait indefinitely for a slot.
CREW_EXECUTION_MAX_QUEUE_WAIT_MS=300000
CREW_EXECUTION_MAX_FIRST_OUTPUT_WAIT_MS=120000
CREW_EXECUTION_FIRST_OUTPUT_GRACE_MS=180000
```

`CREW_MAX_PARALLEL` limits normal executions per Agent. `CREW_SCHEDULED_MAX_PARALLEL` separately
limits scheduled executions per Agent. `MAX_PARALLEL_TOTAL` is the machine-wide process cap shared by
protocol-v1 and legacy work.
Runtime startup is a separate FIFO gate: by default up to ten Claude, Codex, or Kimi processes may be
initializing at a time, and launches are spaced by 500ms. A process leaves the startup gate
only after its runtime-specific ready event; startup timeout cancels the owned process tree.
An accepted execution waits indefinitely for a local and host slot unless it is explicitly cancelled.
After runtime readiness, the daemon waits up to two minutes for the first model event, with a
three-minute grace window for slow providers. When the first-output liveness limit expires, the
supervisor is stopped and the terminal result is written to the execution journal before it is
reported, so the execution remains replayable instead of being silently dropped. First-output
timeouts are retried locally at most twice (three total attempts). Each retry releases its current
machine/host slot and reserves a new one, which puts it behind work already waiting in the queue. A
third timeout is terminal and is not requeued; other failures are not automatically retried.

Local policy can reduce server-requested access and limits; it cannot grant more access than requested.
Provider credentials and configured environment are prepared locally and never carried in execution
control frames.

## 完整本地记忆读取

运行时注入的 `MEMORY.md` 是有界预览，未命中不代表完整文件没有相关记忆。
普通 Agent 工作区会在现有 PATH 目录中提供只读 `crew-memory` 工具，无需 Server 凭证：

```bash
crew-memory search --query '提交前检查'
crew-memory search --query '提交前检查' --after-line 120
crew-memory read --offset 2400 --limit 2000
crew-memory read --file notes/testing.md --offset 0 --limit 2000
```

命令默认返回 JSON。搜索遍历完整指定文件，返回 `line`、`offset`、命中摘要和
`nextAfterLine`；读取按 Unicode 字符偏移分页，使用 `nextOffset` 继续直到 `eof=true`。
后续页可传 `--if-hash <上一页的sha256>`，文件变化时明确停止，不混读不同版本。
每页 JSON 有 12 KiB 上限，输入文件超过 32 MiB 时明确报错，不将未完成搜索伪装为零命中。

工具仅支持 `MEMORY.md` 和 `notes/` 下明确指定的 Markdown 文件，不遍历 pending，
不写入或迁移记忆。搜索结果是原始资料定位，读取明细前仍需确认当前有效索引和正文片段。
这些修改不改变现有 V2 写入上限、候选状态或归档策略，也不提供自动语义召回保证。
工具随 daemon 包发布；旧 daemon 没有该入口时，Agent 使用原生文件搜索与分段读取回退。

## Optional TencentDB Agent Memory Bridge

Execution protocol v1 can opt NowWork Agents into the private TencentDB Agent Memory Panel.
This is an external context backend, not a replacement for NowWork messages, tasks, workspace memory,
or server authorization. It is disabled unless every required variable is present:

```text
CREW_AGENT_MEMORY_URL=https://memory.example/path
CREW_AGENT_MEMORY_INSTANCE_ID=nowwork
CREW_AGENT_MEMORY_USER_KEY=<load the external Agent owner's key from a secret store>
CREW_AGENT_MEMORY_USER_ID=<external Agent owner user id>
CREW_AGENT_MEMORY_TEAM_ID=team-ueasyuv7sx
# Legacy single-Agent fallback during rolling upgrades:
CREW_AGENT_MEMORY_AGENT_ID=agt-vmszw3z170
CREW_AGENT_MEMORY_AGENT_HANDLE=cindy
CREW_AGENT_MEMORY_TIMEOUT_MS=3000
CREW_AGENT_MEMORY_RECALL_LIMIT=8
```

The Agent configuration page owns each non-secret external Agent ID. Turn on `Use long-term memory`,
enter the matching `agt-*` ID, and save. The server carries that mapping in execution protocol v1; the
daemon combines it with the configured instance, user key, and team. Different NowWork Agents must use
different external Agent IDs. `CREW_AGENT_MEMORY_AGENT_ID` and `CREW_AGENT_MEMORY_AGENT_HANDLE` remain
required as a compatibility fallback for older server records during rolling upgrades. Partial daemon
configuration fails startup. Removing all `CREW_AGENT_MEMORY_*` variables and restarting the daemon is
the complete rollback.

The user key must be able to access the selected external Agent's chat-memory asset. Team membership by
itself is insufficient for another owner's private asset: the Panel API returns `ASSET_NOT_ACCESSIBLE`.
Use a key issued to that Agent's owner, or create a dedicated external Agent owned by the daemon account.

On macOS, keep the one-time key in Keychain and resolve it only into the daemon startup environment. Use
a dedicated service name for the `nowwork` instance; do not reuse a key issued by the `default` instance:

```bash
export CREW_AGENT_MEMORY_USER_KEY="$(security find-generic-password \
  -a zuosong -s nowwork-agent-memory-nowwork -w)"
export CREW_AGENT_MEMORY_USER_ID=usr-uebdjxnkbh
export CREW_AGENT_MEMORY_TEAM_ID=team-ueasyuv7sx
export CREW_AGENT_MEMORY_AGENT_ID=agt-vmszw3z170
export CREW_AGENT_MEMORY_AGENT_HANDLE=cindy
```

Do not place the key in a tracked `.env`, shell history, command argument, service description, or log.
The daemon strips every `CREW_AGENT_MEMORY_*` variable from the spawned coding-runtime environment.

Before launch, the bridge reads L3 core memory and recent L1 atomic memory. The Panel does not expose a
semantic-search route, so v1 ranks L1 locally by overlap with the parsed incoming message. Recalled text
is byte-capped and marked as untrusted context that cannot override the current request or system policy.
Timeouts, network errors, invalid responses, and empty memory all fail open without changing execution.

After a successful run, the bridge may import exactly two messages: the parsed current incoming message
and the runtime's final text. It does not upload system prompts, thread/channel history, reasoning, tool
calls, console output, files, attachments, or environment variables. If either message resembles a
credential, authorization header, private key, authenticated database URL, session cookie, or token, the
whole pair is discarded. Failed, cancelled, timed-out, scheduled, legacy protocol-0, unrecognized-prompt,
and unmatched-handle runs are not captured.

See `docs/superpowers/specs/2026-08-08-agent-memory-integration-design.md` for the verified upstream API,
failure matrix, rollout limits, and the server-native follow-up design.

## Code Map

## Local daemon installation root (experimental)

New installations use `~/.nowwork` for machine-local daemon configuration and npm runtimes. The
Workspace persistence root is still selected independently by `NOWWORK_HOME`; it may be a local
directory or an ECS-specific NAS directory. Runtime packages, locks, sockets, PIDs and temporary
files must remain on the machine running the daemon.

The same local root also owns shared daemon control state:
`daemon/managed-services/registry.json`, `daemon/managed-services/installation-leases/`,
`daemon/compute-execution/`, and `logs/sls-spool/`. This keeps service ownership, upgrade leases,
host-wide execution admission, and offline log delivery on the daemon host rather than on NAS.

The selector is controlled by `NOWWORK_LOCAL_HOME_MODE`:

- `auto` (default): use an existing `.nowwork` installation, otherwise preserve an existing `.crew`
  installation; a brand-new profile is created under `.nowwork`.
- `nowwork`: require the `.nowwork` installation root.
- `legacy`: force the old `.crew` root for rollback.

`NOWWORK_STORAGE_MODE` is accepted as a compatibility alias. `NOWWORK_LOCAL_HOME` can override the
local root and should point to a local filesystem, never to the NAS Workspace root; the mount/device
check remains a doctor/migration gate. If the same profile
is found in both roots, startup/install is refused until the duplicate is resolved explicitly. The
old `.crew` tree is not deleted by this compatibility layer.
For shared machine control state, `auto` prefers an existing `~/.nowwork` root; during a mixed
migration window, set `NOWWORK_LOCAL_HOME_MODE=legacy` on services that still use the old registry.

- `src/serve.ts`: connection, negotiation, routing, sync, and legacy boundary.
- `src/daemon-update-eligibility.ts`, `src/daemon-updater.ts`, and `src/daemon-update-controller.ts`:
  managed-update eligibility, exact npm replacement, host-wide admission barrier, and restart handoff.
- `src/execution-runner.ts`: spec admission and lifecycle reporting.
- `src/shared-execution-slots.ts` and `src/runtime-startup-gate.ts`: machine concurrency and startup FIFO.
- `src/local-executor.ts` and `src/execution-supervisor.ts`: runtime process boundary.
- `src/execution-journal.ts`: crash-safe local execution facts.
- `src/runner.ts`: protocol-0 compatibility runner.
- `src/runtimes/`: built-in runtime adapters.
