# Extension Locks Standard

`locks.json` is a shared registry for singleton pi extensions.

Path:

```text
~/.pi/agent/locks.json
```

## Shape

```json
{
  "@llblab/pi-wakeup": {
    "pid": 2590864,
    "cwd": "/home/user/project"
  }
}
```

Top-level keys are extension identities. Values are JSON objects owned by that extension.

## Identity key

Use the most stable available identity:

1. `package.json/name` for npm-style pi packages
2. Directory name when the extension entrypoint is `index.ts` but there is no package name
3. File basename when the extension is a single file

For npm-style package extensions, the canonical value is the `package.json` `name`. Implementations may keep that value as a small local constant when it is clearer than runtime package introspection. The fallback rules are only for unpackaged extensions.

Examples:

```text
extensions/pi-wakeup/package.json name=@llblab/pi-wakeup -> @llblab/pi-wakeup
extensions/pi-wakeup/index.ts without package.json       -> pi-wakeup
extensions/pi-wakeup.ts                                  -> pi-wakeup
```

## Required fields

```json
{
  "pid": 2590864
}
```

`pid` is the process that currently owns the singleton runtime. `cwd` should be stored when ownership is tied to a pi session directory.

During a user-initiated start/connect event, an extension should:

1. Read its lock entry
2. If `pid` is stale, replace the entry
3. If `pid` and `cwd` match the current pi instance, refresh or keep the entry
4. If a live external owner exists, ask interactively whether to move singleton ownership here

## Acquisition timing

Lock writes must be caused by an explicit user-initiated runtime event, such as `/wakeup-start`, `/telegram-connect`, or a confirmed takeover prompt.

Extension initialization and session-start hooks may read `locks.json`, update local status, install ownership watchers, and resume local work when the existing lock already points at the current `pid`/`cwd`. After a full process restart, a session-start hook may replace a stale lock from the same `cwd` to restore explicitly requested ownership. They must not create ownership from an inactive lock, take over a live external owner, or replace a stale lock from another directory by themselves. Such locks should stay visible as state until the user runs the start/connect command. Session replacement should suspend local runtime work and ownership watchers without releasing the lock, so the next session in the same `pid`/`cwd` can resume from explicit ownership.

## Optional fields

Extensions may add fields when useful:

```json
{
  "pid": 2590864,
  "cwd": "/repo/project",
  "mode": "connected",
  "target": "telegram",
  "updatedAt": "2026-04-28T00:00:00.000Z"
}
```

Keep optional fields compact. Do not print them in normal UI unless they help the user act.

## Ownership rules

- One top-level key per singleton extension
- An extension may only mutate its own key
- Other keys must be preserved exactly
- If `cwd` is present, active-here ownership means both `pid` and `cwd` match the current pi instance
- Human-readable diagnostics should say `active here`, `active elsewhere`, or `stale`
- Debug data belongs in `locks.json`, not in normal status output

## Runtime status

Singleton extensions with footer/status presence should expose quiet but explicit local state. For example, pi-wakeup uses:

- `wakeup off` when this pi instance does not own the singleton runtime
- `wakeup on` when this pi instance owns the runtime but has no pending wake-up detail to show
- `wakeup [16:32:39]` when the runtime owns scheduled work and can show the next countdown

## Interactive takeover

Start/connect commands should make singleton moves easy:

1. If no live owner exists, take ownership without an extra prompt
2. If a live external owner exists, ask whether to move singleton ownership to this pi instance
3. On confirmation, write the current `{ "pid": ..., "cwd": ... }` to this extension's key in `locks.json`
4. The previous owner must notice that `locks.json` no longer points at its own `pid`/`cwd` and stop local runtime work without deleting the new lock

Takeover prompts should use the extension name as the dialog title, then the question, a blank line, and source/target lines:

```text
pi-wakeup
move singleton lock here?

from: pid 2590864, cwd /old
to: /new
```

Avoid repeating the extension name in the body. Color is encouraged: extension title/name accent, question warning, `from:`/`to:` muted.

The previous owner may use `fs.watch`, mtime polling, or an existing status/timer tick. Long-lived watchers should compare against a snapshotted `pid`/`cwd` identity rather than a live pi context object, because session replacement such as `/new` makes captured contexts stale. The important contract is graceful local shutdown after ownership mismatch.

## Atomicity

Current baseline is read-modify-write JSON. This is enough for interactive pi singleton starts.

If multiple instances may start concurrently, use an atomic helper later:

- Lock file around `locks.json`, or
- Temp file + rename with conflict checks, or
- OS-level exclusive open for a short critical section

## Migration

Migrations from legacy lock files or legacy keys should be one-off cleanup work. Runtime ownership should read and write only `locks.json` under the canonical identity key.
