# Write routing policy

This document defines the shared policy used by the staged Tier 3 daemon
rollout tracked in #1963.

This foundation PR does not change existing hook or CLI routing. It provides
one tested policy model that later hook and CLI PRs can consume without
inventing different fallback rules.

## Policies

`direct`

    Always execute through the existing direct local path.

`prefer`

    Use an available daemon. A caller that is allowed to start the daemon may
    do so. Otherwise, fall back to the direct path.

`require`

    Use an available daemon. A caller that is allowed to start the daemon may
    do so. If neither is possible, block the operation. Never fall back to a
    direct ChromaDB writer.

## Concrete routing outcomes

The shared decision function returns one of:

- `direct`
- `daemon`
- `blocked`

It also reports whether the caller should auto-start the daemon and why the
route was selected.

Hooks generally pass `daemon_can_start=False` because hook execution has a
tight latency budget.

Interactive CLI commands can pass `daemon_can_start=True`.

## Configuration

Global environment policy:

    MEMPALACE_WRITE_ROUTING=direct|prefer|require

Hook-specific environment policy:

    MEMPALACE_HOOK_WRITE_ROUTING=direct|prefer|require

CLI-specific environment policy:

    MEMPALACE_CLI_WRITE_ROUTING=direct|prefer|require

Configuration-file shape:

    {
      "write_routing": {
        "default": "direct",
        "hooks": "prefer",
        "cli": "require"
      }
    }

## Precedence

For hooks:

1. `MEMPALACE_HOOK_WRITE_ROUTING`
2. `MEMPALACE_WRITE_ROUTING`
3. legacy `MEMPALACE_HOOKS_DAEMON`
4. `write_routing.hooks`
5. `write_routing.default`
6. legacy `hooks.daemon`
7. `direct`

For CLI writes:

1. `MEMPALACE_CLI_WRITE_ROUTING`
2. `MEMPALACE_WRITE_ROUTING`
3. `write_routing.cli`
4. `write_routing.default`
5. `direct`

## Backward compatibility

The existing `MEMPALACE_HOOKS_DAEMON` environment variable and
`hooks.daemon` config value remain supported.

Legacy true values map to `prefer`.

Legacy false values map to `direct`.

The existing `MempalaceConfig.hook_use_daemon` property is intentionally
unchanged in this PR. Hook and CLI behavior remains unchanged until their
policy-aware rollout PRs land.

## Invalid policy values

New policy settings accept only:

- `direct`
- `prefer`
- `require`

Invalid values fail with a source-specific error rather than silently falling
back. This is important because silently turning a misspelled `require` into a
direct write would violate the safety purpose of the policy.

## Local backend single-writer safety

File-backed backends such as `chroma`, `sqlite_exact`, and Milvus Lite support
exactly one writable process per palace. Serializing individual calls is not
enough because each long-lived process can retain SQLite/WAL, FTS, or vector
index state between calls.

- A writable daemon owns the palace writer lease for its full lifetime.
- Writable MCP HTTP acquires that lease before binding, holds it through the
  full serving lifetime, and releases it after active requests stop.
- MCP stdio opens `sqlite_exact` read-only until it acquires the writer lease.
  It may therefore coexist for reads; mutating tools refuse while another
  process owns the lease and reopen writable storage after that owner exits.
- Read-only MCP HTTP may coexist with the writer.
- Read-only `sqlite_exact` clients use an immutable connection for a clean
  checkpointed database, or `mode=ro` when an active writer's complete WAL
  sidecar pair must remain visible. Both paths enable `query_only` and skip
  schema, WAL, FTS, migration, and metadata initialization.
- Direct CLI and hook writes must not run beside a writable daemon or MCP HTTP
  owner. Route them through the daemon with `require` when the daemon owns the
  palace.
- Direct `sqlite_exact` collection mutations contend for the same palace lease,
  and full LLM closet regeneration owns it before opening collections or
  calling the configured model.

`MEMPALACE_MCP_ALLOW_PEER_WRITER` cannot bypass this protection for local
file-backed or unknown plugin backends. It is retained only for explicitly
remote service backends (`qdrant`, `pgvector`, and Milvus server/Zilliz Cloud)
that coordinate concurrent clients themselves. Milvus Lite remains protected
as local file-backed storage.

Do not delete or unlink a live palace lock to recover ownership. Stop the
owning process cleanly; the operating system releases its lock automatically.
If corruption is suspected, back up the palace and run integrity/repair
operations offline, with no writable service running.

## Follow-up PRs

Hook-triggered writes now consume this policy; see
`docs/hook-write-routing.md`.

The remaining rollout PR will apply the policy to routine CLI writes.

Maintenance operations such as repair, migration, and index rebuild are not
ordinary routed writes. They require a separate exclusive-maintenance policy.
