@hagicode/cli 0.1.0-dev.41.1.8543fd3 → 0.1.0-dev.43.1.03d9a6a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("./chunks/main-C3T3Iu-a.js");s.runCli().then(e=>{e!==0&&(process.exitCode=e)}).catch(e=>{const n=e instanceof Error?e.stack??e.message:String(e);process.stderr.write(`${n}
2
+ Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("./chunks/main-mvnmEVmb.js");s.runCli().then(e=>{e!==0&&(process.exitCode=e)}).catch(e=>{const n=e instanceof Error?e.stack??e.message:String(e);process.stderr.write(`${n}
3
3
  `),process.exitCode=s.EXIT_CODES.UNKNOWN});
package/dist/main.js CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./chunks/main-C3T3Iu-a.js");exports.createCliApp=e.createCliApp;exports.runCli=e.runCli;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./chunks/main-mvnmEVmb.js");exports.createCliApp=e.createCliApp;exports.runCli=e.runCli;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hagicode/cli",
3
- "version": "0.1.0-dev.41.1.8543fd3",
3
+ "version": "0.1.0-dev.43.1.03d9a6a",
4
4
  "description": "HagiCode CLI for project, proposal, vault, chat, and AutoTask management.",
5
5
  "homepage": "https://github.com/HagiCode-org/cli#readme",
6
6
  "bugs": {
package/skills/README.md CHANGED
@@ -28,6 +28,7 @@ Repository-local commands under `repos/cli` are maintainer workflows only.
28
28
  - [`hagi/references/session-data.md`](hagi/references/session-data.md) - inside a HagiCode session: read-only data commands and the actions that need an explicit request
29
29
  - [`hagi/references/proposal-flow.md`](hagi/references/proposal-flow.md) - explicit proposal lifecycle guidance
30
30
  - [`hagi/references/openspec-workflow.md`](hagi/references/openspec-workflow.md) - the OpenSpec stage each proposal command starts and how to follow a run
31
+ - [`hagi/references/resource-lock.md`](hagi/references/resource-lock.md) - the shared resource lock: owner, exit codes, the watcher protocol and requisition rules
31
32
  - [`hagi/references/super-commit.md`](hagi/references/super-commit.md) - Super Commit through the CLI: prompt-id start, options, result and explicit-request rule
32
33
  - [`hagi/references/local-development.md`](hagi/references/local-development.md) - maintainer-only source-tree workflows
33
34
  - [`hagi/references/validation.md`](hagi/references/validation.md) - post-change validation steps
@@ -13,6 +13,7 @@ Use this skill when the user asks to:
13
13
  - run or verify the `hagi` CLI
14
14
  - explain or execute `project`, `proposal`, `chat`, `hagitask`, `vault`, `plan`, or `skills` commands
15
15
  - explain the OpenSpec workflow behind `proposal` commands, which stage each command starts, or how to follow a run until it settles
16
+ - take turns on a shared resource (a game server, a test environment) with other agents through `hagi agent lock`
16
17
  - run, explain or check a Super Commit, which commits the uncommitted changes of a code repository through a HagiTask session
17
18
  - inspect CLI flags, environment variables, exit codes, or JSON automation behavior
18
19
  - validate proposal-session flows against a backend API
@@ -37,6 +38,7 @@ Use this skill when the user asks to:
37
38
  5. Source-tree development in `repos/cli` -> read [`references/local-development.md`](references/local-development.md)
38
39
  6. Post-change validation -> read [`references/validation.md`](references/validation.md)
39
40
  7. The user's own data, inside a HagiCode session -> read [`references/session-data.md`](references/session-data.md)
41
+ 8. A shared resource that other agents also use, or any `hagi agent lock` command -> read [`references/resource-lock.md`](references/resource-lock.md)
40
42
 
41
43
  When `hagi` is loaded as a built-in skill inside a HagiCode session, read item 7 first for any question about the user's own data or skills, before the other references.
42
44
 
@@ -81,6 +83,7 @@ The current top-level command families are:
81
83
  - `hagitask`
82
84
  - `plan`
83
85
  - `skills`
86
+ - `agent`
84
87
 
85
88
  `project repositories --id <project-id>` is read-only. It lists one project's repositories with each path relative to the project root (for example `repos/cli`). Those paths are the values a proposal's `Repository scope` names; see [`references/proposal-flow.md`](references/proposal-flow.md#repository-scope).
86
89
 
@@ -90,6 +93,16 @@ HagiTask sessions (`hagi hagitask`) run a preset task with a structured input. `
90
93
 
91
94
  Each family also reads one session by id. `proposal get` and `hagitask get` read a session; with `--wait` they poll until its status settles, so a run can be followed without guessing. `proposal messages` and `hagitask messages` read a session's messages, and `--last 1` reads the final message of a run. The stage behind each proposal command is in [`references/openspec-workflow.md`](references/openspec-workflow.md). Super Commit is started with `hagitask create --prompt-id auto-compose-commit.<locale>`; see [`references/super-commit.md`](references/super-commit.md).
92
95
 
96
+ ## Resource lock
97
+
98
+ `hagi agent lock` is a fair queue lock for a resource that several agents share. Read [`references/resource-lock.md`](references/resource-lock.md) before the first lock command. The protocol is watcher-based:
99
+
100
+ - Pass your own session ID as the owner: `hagi agent lock acquire --lock-id eu-1/duckov --owner sess-7f2c`. Every example is `hagi agent lock ...`; there is no `hagi lock`.
101
+ - After `acquire`, read the windows. `Holding`: start the heartbeat watcher (a Monitor running `hagi agent lock heartbeat ...`, or a background process or sub-agent where there is no Monitor) before any work. `Queued`: start a continuous `hagi agent lock acquire --wait ...` watcher; do not sleep and re-check.
102
+ - When all tasks are done, stop the heartbeat watcher and then run `hagi agent lock release`. Stopping alone releases the lock after 10 minutes without a heartbeat; the lock is also taken back after 24 hours. The heartbeat interval is 2 minutes.
103
+ - When a watcher reports loss (exit code `32`), stop using the resource at once. After losing track (for example after compaction), repeat `acquire` with the same owner; it is safe.
104
+ - Never run `hagi agent lock requisition` unless the user explicitly ordered a forced take; nothing else is a requisition, and otherwise you always queue. When ordered, quote the request in `--reason`, start the heartbeat watcher and tell the user which owner was displaced. When you are displaced, stop at once, tell the user, queue again only if the task still needs the resource, and never requisition back. A displaced agent can use the resource until its next heartbeat (up to 2 minutes), and the lock cannot stop work already running on the resource.
105
+
93
106
  ## Source of truth
94
107
 
95
108
  - Canonical writable skill source: `repos/cli/skills/hagi/`
@@ -102,5 +115,6 @@ Each family also reads one session by id. `proposal get` and `hagitask get` read
102
115
  - [`references/proposal-flow.md`](references/proposal-flow.md) - explicit proposal lifecycle sequence and command examples
103
116
  - [`references/openspec-workflow.md`](references/openspec-workflow.md) - the OpenSpec stage each proposal command starts, stage boundaries, who drives each step, and the monitoring rule
104
117
  - [`references/super-commit.md`](references/super-commit.md) - Super Commit: starting it by prompt id, its options, result reading and the explicit-request rule
118
+ - [`references/resource-lock.md`](references/resource-lock.md) - the shared resource lock: numbers, owner, exit codes, the watcher protocol, the seven agent rules and requisition
105
119
  - [`references/local-development.md`](references/local-development.md) - maintainer-only development, build, and repository internals
106
120
  - [`references/validation.md`](references/validation.md) - targeted verification after documentation or code changes
@@ -92,9 +92,10 @@ hagi
92
92
  |-- chat list | create | archive | delete | send
93
93
  |-- hagitask list | schema | create | send | get | messages
94
94
  |-- plan list | get | create | update | enable | disable | delete | run
95
- `-- skills local list | enable | disable | uninstall
96
- gallery search | detail | install
97
- trusted list | upsert | delete | reset
95
+ |-- skills local list | enable | disable | uninstall
96
+ | gallery search | detail | install
97
+ | trusted list | upsert | delete | reset
98
+ `-- agent lock acquire | status | renew | heartbeat | release | requisition | list
98
99
  ```
99
100
 
100
101
  ## Project repositories
@@ -140,6 +141,24 @@ hagi proposal messages --session-id <session-id> --last 1 --json
140
141
  - `--timeout`, `--interval` and `--grace` are seconds and need `--wait`. `--interval` must be less than `--timeout`. A timeout exits `31`.
141
142
  - Which stage each command starts, and how to follow a run, is in [openspec-workflow.md](openspec-workflow.md).
142
143
 
144
+ ## Session metadata
145
+
146
+ `chat create` and `proposal create` take write-once metadata entries. An orchestrator marks the sessions it starts with them, for example a master marker and worker markers, and finds them again through `userMetadata`.
147
+
148
+ ```bash
149
+ hagi chat create --title "Master" --project-id <project-id> --metadata role=master --metadata batch=2026-11-07 --json
150
+ hagi chat create --title "Worker 1" --project-id <project-id> --metadata role=worker --metadata master=<master-session-id> --json
151
+ printf '{"role":"worker","master":"<master-session-id>"}' | hagi chat create --title "Worker 2" --project-id <project-id> --metadata-json - --json
152
+ hagi proposal create --project-id <project-id> --chief-complaint "<text>" --default-title --metadata role=worker --metadata master=<master-session-id> --json
153
+ ```
154
+
155
+ - `--metadata key=value` is repeatable. The text is split at the first `=`, so a value may contain `=`.
156
+ - `--metadata-json <file|->` reads a JSON object of string values from a file, or from stdin with `-`. It is repeatable.
157
+ - A key may appear once across both options. A missing `=`, an empty key, a repeated key, or JSON that is not an object of string values exits `2` before any request is sent.
158
+ - Backend limits: at most 16 entries; a key of 1 to 64 characters matching `^[a-z][a-z0-9._-]{0,63}$`; a value of 1 to 512 characters with no control characters. The backend's message is printed as it is, and the session is not created.
159
+ - Write-once: the entries are set at creation and cannot be changed later. Do not put secrets in them: every reader of the session can see them.
160
+ - Output: `--json` of `chat create`, `chat list`, `proposal create` and `proposal get` carries `userMetadata` (an object of string values) when the session has entries. The human output adds one `userMetadata` line, keys sorted. A session without entries has no `userMetadata` field.
161
+
143
162
  ## HagiTask commands
144
163
 
145
164
  A HagiTask is a preset task with a structured input. Use the schema-first flow: list the tasks, read one task's schema, write the input file from the schema, then create the session.
@@ -262,6 +281,28 @@ hagi skills local disable --skill-name hagi --json
262
281
  - Skill recommendation and install progress streaming are not part of the CLI. `skills gallery search`, `detail` and `install` are the discovery and install path.
263
282
  - `skills gallery detail` adds skills.sh audit ratings. `--json` keeps the detail fields and adds `audit: { providers: [...] }`, one entry per provider with `provider` and, when supplied, `risk`, `alerts`, `score` and `analyzedAt`. Human output appends one `skills.sh audit (<provider>)` line per provider, with `--` for missing values. If the audit fails or has no data, the command still exits `0` and `audit.providers` is `[]`. A detail failure fails the command as before. The audit never changes the install command preview or installability.
264
283
 
284
+ ## Agent lock commands
285
+
286
+ `hagi agent lock` takes turns on a shared resource with other agents. The full protocol, the numbers and the seven agent rules are in [`resource-lock.md`](resource-lock.md); read it before using these commands. There is no top-level `hagi lock`.
287
+
288
+ ```bash
289
+ hagi agent lock acquire --lock-id eu-1/duckov --owner sess-7f2c
290
+ hagi agent lock acquire --server eu-1 --game duckov --owner sess-7f2c --wait --timeout 900
291
+ hagi agent lock status --lock-id eu-1/duckov --owner sess-7f2c --json
292
+ hagi agent lock heartbeat --lock-id eu-1/duckov --owner sess-7f2c --epoch 7
293
+ hagi agent lock renew --lock-id eu-1/duckov --owner sess-7f2c --epoch 7
294
+ hagi agent lock release --lock-id eu-1/duckov --owner sess-7f2c --epoch 7
295
+ hagi agent lock requisition --lock-id eu-1/duckov --owner sess-7f2c --reason "User order: take the lock now"
296
+ hagi agent lock list --prefix eu-1/
297
+ ```
298
+
299
+ - Name the lock with `--lock-id <id>` or with `--server <name> --game <name>` (not both forms). The owner is your own session ID, passed as `--owner`; without it the CLI reads `HAGI_LOCK_OWNER`, then `HAGI_SESSION_ID`, and otherwise exits `2`.
300
+ - `acquire` takes `--wait`, `--timeout <seconds>` (default 900, needs `--wait`), `--lease-seconds <n>` and `--label <text>`. Without `--wait` it exits `0` for `Holding` and `Queued`. With `--wait` it exits `0` only when the owner holds the lock, `31` on timeout and `32` when the queue entry was dropped.
301
+ - `status` takes `--wait-seconds <n>`. `renew` and `heartbeat` take `--epoch <n>` and `--lease-seconds <n>`; `heartbeat` also takes `--interval <seconds>` (default 120) and `--verbose`. `release` takes `--epoch <n>`. `requisition` requires `--reason <text>` and takes `--label <text>`. `list` takes `--prefix <text>`.
302
+ - `heartbeat` is long-running and quiet on success. Run it under a watcher; it exits `32` when the lock is lost and `0` when stopped, without releasing.
303
+ - Timing: 10-minute feedback timeout, 24-hour maximum hold, 2-minute heartbeat interval, 30-second claim window, 60-second waiter TTL.
304
+ - Never run `requisition` unless the user explicitly ordered a forced take.
305
+
265
306
  ## Exit codes
266
307
 
267
308
  Stable non-zero exits used by the CLI. The HTTP numbers are the equivalents of the gRPC status the backend answered with:
@@ -275,7 +316,8 @@ Stable non-zero exits used by the CLI. The HTTP numbers are the equivalents of t
275
316
  | `24` | HTTP 404 |
276
317
  | `29` | HTTP 409 |
277
318
  | `30` | Operation failed: the backend returned a refusal, for example `skills local enable` or `disable` on a name that is not a built-in skill, an invalid MonoSpecs configuration, or a failed MonoSpecs clone |
278
- | `31` | Wait timed out: `--wait` on `proposal get` or `hagitask get` reached `--timeout` before the session settled. The last status is printed. Not a success, and not a backend error |
319
+ | `31` | Wait timed out: `--wait` on `proposal get`, `hagitask get` or `agent lock acquire` reached `--timeout` before the session settled. The last status is printed. Not a success, and not a backend error |
320
+ | `32` | Lock lost: a `hagi agent lock` command learned that the owner no longer holds the lock (expired, taken back at the 24-hour cap, stale epoch, requisitioned). Stop using the resource. Distinct from `31` |
279
321
  | `40` | Other HTTP 4xx |
280
322
  | `50` | HTTP 5xx |
281
323
  | `60` | Transport or connection failure |
@@ -173,6 +173,33 @@ npx @hagicode/cli proposal complete --session-id <session-id>
173
173
  npx @hagicode/cli proposal send --session-id <session-id> --content "Please continue"
174
174
  ```
175
175
 
176
+ ## Session metadata
177
+
178
+ `proposal create` accepts `--metadata key=value` (repeatable) and `--metadata-json <file|->` (a JSON object of string values; `-` reads stdin). Use them to mark the sessions of one orchestrated run: a master marker on the orchestrator and worker markers on each proposal, so the run can find its sessions again.
179
+
180
+ ```bash
181
+ MASTER_ID=$(hagi chat create --title "Batch master" --project-id <project-id> --metadata role=master --metadata batch=2026-11-07 --json | json_field '.session.sessionId')
182
+
183
+ hagi proposal create \
184
+ --project-id <project-id> \
185
+ --chief-complaint "Repository scope
186
+ Writable repositories:
187
+ - repos/web
188
+ Read-only repositories:
189
+ - none
190
+
191
+ Tighten the settings page copy" \
192
+ --default-title \
193
+ --metadata role=worker \
194
+ --metadata master="$MASTER_ID" \
195
+ --json
196
+ ```
197
+
198
+ - Keys: 1 to 64 characters matching `^[a-z][a-z0-9._-]{0,63}$`. Values: 1 to 512 characters with no control characters. At most 16 entries. The backend checks these and its message is shown as it is.
199
+ - Each key appears once across both options. A repeat, a missing `=`, or a non-string JSON value exits `2` with no request sent.
200
+ - Metadata is write-once and visible to every reader of the session. Do not put secrets in it.
201
+ - The output `session` object carries `userMetadata` when the session has entries. Read it to confirm the markers were stored.
202
+
176
203
  ## Validation focus
177
204
 
178
205
  After each lifecycle step:
@@ -0,0 +1,135 @@
1
+ # Resource lock
2
+
3
+ `hagi agent lock` lets several agents take turns on one shared resource, for example a game server that only one agent may deploy to or test on at a time. The lock is a fair queue held by the backend. It is cooperative: it cannot stop an action that is already running on the remote resource, so every agent has to follow the protocol below.
4
+
5
+ Every command is `hagi agent lock <verb>`. There is no top-level `hagi lock`. When the package is not installed, replace `hagi` with `npx @hagicode/cli`.
6
+
7
+ ## Numbers
8
+
9
+ | Number | Value | Meaning |
10
+ | --- | --- | --- |
11
+ | Feedback timeout | 10 minutes | A holder that sends no heartbeat for 10 minutes loses the lock. Stopping the heartbeat alone releases the lock after this time. |
12
+ | Maximum hold | 24 hours | Absolute, counted from the grant. The lock is taken back at the cap even when heartbeats keep arriving. |
13
+ | Heartbeat interval | 2 minutes (120 s) | How often `heartbeat` renews. One lost beat is survivable. |
14
+ | Claim window | 30 seconds | After a handoff the new holder must confirm (status, renew or acquire) within 30 s, or the lock moves on. A watcher confirms at once. |
15
+ | Waiter TTL | 60 seconds | A queued owner that is not polling for 60 s is dropped from the queue. |
16
+ | Default wait timeout | 900 seconds | `acquire --wait` gives up after this long. Change it with `--timeout <seconds>`. |
17
+
18
+ `--lease-seconds` can request a shorter feedback timeout (30 to 600 seconds, never more than 10 minutes).
19
+
20
+ ## Addressing and owner
21
+
22
+ - Name the lock with `--lock-id <id>` (for example `eu-1/duckov`) or with the pair `--server <name> --game <name>`, which means `<server>/<game>`. Do not mix the two forms.
23
+ - The owner is **your own session ID**: pass it as `--owner <id>`, for example `--owner sess-7f2c`. When `--owner` is absent the CLI reads `HAGI_LOCK_OWNER`, then `HAGI_SESSION_ID`. With none of the three it exits `2` and makes no request. The CLI never invents an owner ID.
24
+ - Use the same owner ID for every command about one lock. Two agents that share one owner ID act as one owner, and nothing detects that.
25
+
26
+ ```bash
27
+ hagi agent lock acquire --lock-id eu-1/duckov --owner sess-7f2c
28
+ hagi agent lock acquire --server eu-1 --game duckov --owner sess-7f2c --label "deploy test build" --json
29
+ ```
30
+
31
+ ## Commands
32
+
33
+ | Command | Use |
34
+ | --- | --- |
35
+ | `hagi agent lock acquire` | Take the lock or join the queue. Without `--wait` it is one call and exits `0` for both `Holding` and `Queued`. With `--wait` it waits for the turn and exits `0` only when you hold the lock. It has no force option. Options: `--wait`, `--timeout <seconds>`, `--lease-seconds <n>`, `--label <text>`. |
36
+ | `hagi agent lock status` | Read your own state: `Holding`, `Queued`, `Expired`, `Requisitioned` or `None`. Exit `0` for all of them. `--wait-seconds <n>` is a long poll (the server caps it at 25). |
37
+ | `hagi agent lock heartbeat` | Keep a held lock alive. Long-running and quiet on success; run it under a watcher. Options: `--epoch <n>`, `--interval <seconds>`, `--lease-seconds <n>`, `--verbose`. |
38
+ | `hagi agent lock renew` | One renewal, a single heartbeat. Prefer `heartbeat`. Options: `--epoch <n>`, `--lease-seconds <n>`. |
39
+ | `hagi agent lock release` | Give the lock back or leave the queue. Exit `0` whether or not something was released. Option: `--epoch <n>`. |
40
+ | `hagi agent lock requisition` | Emergency take that displaces the holder. Only on an explicit user order (rules 6 and 7). Requires `--reason <text>`; option `--label <text>`. |
41
+ | `hagi agent lock list` | List locks that have a holder or a queue. Option: `--prefix <text>`. |
42
+
43
+ Every command except `list` also takes `--lock-id`, `--server`, `--game` and `--owner`; all seven take the shared `--json` option. The answers carry the expected-time windows: when the lock is valid until, the hard cap, the check-in window and, for a queued owner, the turn window. Times are server times; they are guidance for when to expect something, not wake-up schedules. An epoch identifies one grant; the `acquire` answer prints it, and `heartbeat` reads it itself when `--epoch` is omitted.
44
+
45
+ ## Exit codes
46
+
47
+ | Exit code | Meaning |
48
+ | --- | --- |
49
+ | `0` | Success. For `acquire --wait`: you hold the lock (your turn). For `heartbeat`: it was stopped by SIGINT or SIGTERM (the lock is not released). |
50
+ | `2` | Usage error: missing owner, conflicting lock addressing, a bad number, `--timeout` without `--wait`. No request was sent. |
51
+ | `20` / `21` / `23` / `24` / `29` | Backend refusal: invalid argument (for example a bad lock ID, owner or reason), unauthenticated, permission denied, not found, conflict. |
52
+ | `30` | `acquire --wait` was interrupted (the queue entry is released), or the backend refused the operation. |
53
+ | `31` | Wait timeout: `acquire --wait` reached `--timeout` without the turn. The queue entry it created was released. |
54
+ | `32` | `LOCK_LOST`: the lock is no longer yours (lease expired, taken back at the 24-hour cap, stale epoch, not the holder, or requisitioned). Stop using the resource at once. Distinct from `31`. Printed by `heartbeat`, `renew`, and by `acquire --wait` when the queue entry was dropped. |
55
+ | `40` / `50` | Other HTTP 4xx / HTTP 5xx equivalent. |
56
+ | `60` | Transport failure. A watcher retries these itself and never treats them as loss. |
57
+ | `70` | Unknown error. |
58
+
59
+ ## The watcher protocol
60
+
61
+ A watcher is a long-running command whose output lines reach you as notifications. Use a Monitor that runs the command. Where there is no Monitor, run the same command as a background process whose output you read, or in a background sub-agent that reports back. State the rule in terms of what the watcher must do; the provider's tool is a detail.
62
+
63
+ ### 1. Acquire, read the windows, start the heartbeat watcher
64
+
65
+ ```bash
66
+ hagi agent lock acquire --lock-id eu-1/duckov --owner sess-7f2c --label "deploy test build"
67
+ ```
68
+
69
+ Read the status and the windows in the answer.
70
+
71
+ - `Holding`: start the heartbeat watcher **before any work**, and keep it running while you use the resource.
72
+
73
+ ```bash
74
+ hagi agent lock heartbeat --lock-id eu-1/duckov --owner sess-7f2c --epoch 7
75
+ ```
76
+
77
+ It beats at once, then every 2 minutes. It prints one start line, then nothing while all is well. It prints a line, and exits `32`, when the lock is lost. It warns once when the backend has been unreachable for over 60 seconds and again on recovery, says "may already be lost" when the lease deadline passed during an outage, and warns 30 minutes and 5 minutes before the 24-hour cap. `--verbose` adds one line per beat. A transport error is never reported as loss.
78
+ - `Queued`: start a continuous wait watcher and carry on with other work, or stop. Do not sleep until the window and then check.
79
+
80
+ ```bash
81
+ hagi agent lock acquire --lock-id eu-1/duckov --owner sess-7f2c --wait
82
+ ```
83
+
84
+ It prints one line when your position changes, and exits `0` when the turn comes (start the heartbeat watcher then), `31` on timeout (default 900 s), `32` if the entry was dropped. Pass `--timeout <seconds>` for a different limit.
85
+
86
+ ### 2. Finish: stop the heartbeat, then release
87
+
88
+ When all tasks are done, stop the heartbeat watcher, then release:
89
+
90
+ ```bash
91
+ hagi agent lock release --lock-id eu-1/duckov --owner sess-7f2c --epoch 7
92
+ ```
93
+
94
+ Stopping the heartbeat watcher (SIGINT or SIGTERM) exits `0` and does not release. Stopping alone also ends the hold, because the backend releases the lock after 10 minutes without feedback, but `release` hands the resource over at once.
95
+
96
+ ### 3. Loss and recovery
97
+
98
+ When a watcher reports that the lock was lost, stop using the resource immediately. Retrying does not extend the 24-hour cap or the 10-minute timeout.
99
+
100
+ When you lose track of whether you hold the lock (for example after your context was compacted), run `acquire` again with the same owner. It is safe: the first acquire stays valid (same epoch, same 24-hour clock) and the answer gives the windows. It does **not** refresh the lease, so it is not a heartbeat. Then check that the heartbeat watcher is running, and start it if it is not.
101
+
102
+ ## The seven rules for the agent
103
+
104
+ 1. After an `acquire`, read the windows in the answer. If the status is `Holding`, start the heartbeat watcher (a Monitor running `hagi agent lock heartbeat ...`) before doing any work. Where Monitor is not available, run the same command as a background process or in a background sub-agent whose output you read.
105
+ 2. If the status is `Queued`, start a continuous wait watcher (a Monitor, or a sub-agent, running `hagi agent lock acquire --wait ...`) and carry on with other work or stop. Do not sleep until the window and then check: the waiter TTL (60 s) would drop your queue entry and the claim window (30 s) would take back a handoff nobody noticed. The window is a guide to when to expect the turn, not a wake-up schedule.
106
+ 3. When all tasks are done, stop the heartbeat watcher and then release the lock. Stopping the heartbeat alone is enough, because the backend releases the lock after 10 minutes without feedback, but `release` hands the resource over at once.
107
+ 4. When a watcher reports that the lock was lost, stop using the resource immediately. The 24-hour cap and the 10-minute timeout are not extended by retrying.
108
+ 5. If you lose track of whether you hold a lock (for example after your context is compacted), call `acquire` again with the same owner: it is safe, and the answer gives the windows. Then check that the heartbeat watcher is running.
109
+ 6. Never requisition unless the user has explicitly told you to force-take the lock. Your own judgment that something is urgent is not enough, and nothing is a requisition without an explicit user order. In every other case, call `acquire` and wait in the queue.
110
+ 7. When the user does order a requisition, run `requisition` with a reason that quotes the request, start the heartbeat watcher, and tell the user which owner was displaced. When you are the one displaced, stop using the resource immediately, tell the user, queue again only if your task still needs the resource, and never requisition back.
111
+
112
+ ## Requisition
113
+
114
+ `requisition` takes the lock at once. It is its own command; `acquire` has no force option and `acquire --wait` never requisitions.
115
+
116
+ ```bash
117
+ hagi agent lock requisition --lock-id eu-1/duckov --owner sess-7f2c --reason "User order: 'take the duckov lock now, the demo starts in five minutes'"
118
+ ```
119
+
120
+ - Run it only after the user has explicitly ordered a forced take (rule 6). Quote the order in `--reason` (1 to 200 characters). The displaced agent is shown the reason and the backend logs it.
121
+ - The answer prints the new epoch, the valid-until time and the windows. Start the heartbeat watcher at once (rule 7), then tell the user which owner was displaced.
122
+ - If you already hold the lock, nothing changes. Waiters keep their order. The displaced owner is not queued again.
123
+ - When you are displaced, your next heartbeat prints the requester, the time and the reason, and exits `32`. A status read shows `Requisitioned` for 10 minutes, then `None`. Stop at once, tell the user, queue again with `acquire` only if the task still needs the resource, and never requisition back.
124
+ - A displaced agent may keep using the resource until its next heartbeat, up to 2 minutes. The lock cannot stop work that is already running on the remote resource.
125
+
126
+ ## Failure handling
127
+
128
+ | Situation | What happens |
129
+ | --- | --- |
130
+ | Holder crashes, or stops its heartbeat without releasing | After 10 minutes the lock goes to the head of the queue; the old owner reads `Expired`. |
131
+ | You never started a heartbeat watcher | The lock is released after 10 minutes; your next check-in reports loss. |
132
+ | The heartbeat watcher dies while you live | Same as above. Check the watcher after a long step. |
133
+ | The backend is unreachable | The heartbeat retries with 1 to 10 s backoff and warns after 60 s. It never reports loss by itself; the 10-minute timeout bounds the damage. |
134
+ | You sleep instead of watching in the queue | The waiter TTL (60 s) removes your entry; the next call reads `Expired` and you queue again at the back. |
135
+ | A slow old holder acts after being replaced | The epoch makes its renew and release fail; the new holder is unaffected. |