@hagicode/cli 0.1.0-dev.42.1.b13f8d2 → 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/README.md +39 -4
- package/dist/chunks/main-mvnmEVmb.js +201 -0
- package/dist/cli.js +1 -1
- package/dist/main.js +1 -1
- package/package.json +1 -1
- package/skills/README.md +1 -0
- package/skills/hagi/SKILL.md +14 -0
- package/skills/hagi/references/cli-usage.md +28 -4
- package/skills/hagi/references/resource-lock.md +135 -0
- package/dist/chunks/main-CGAPe6tb.js +0 -179
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-
|
|
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-
|
|
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.
|
|
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
|
package/skills/hagi/SKILL.md
CHANGED
|
@@ -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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
|
@@ -280,6 +281,28 @@ hagi skills local disable --skill-name hagi --json
|
|
|
280
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.
|
|
281
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.
|
|
282
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
|
+
|
|
283
306
|
## Exit codes
|
|
284
307
|
|
|
285
308
|
Stable non-zero exits used by the CLI. The HTTP numbers are the equivalents of the gRPC status the backend answered with:
|
|
@@ -293,7 +316,8 @@ Stable non-zero exits used by the CLI. The HTTP numbers are the equivalents of t
|
|
|
293
316
|
| `24` | HTTP 404 |
|
|
294
317
|
| `29` | HTTP 409 |
|
|
295
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 |
|
|
296
|
-
| `31` | Wait timed out: `--wait` on `proposal get` or `
|
|
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` |
|
|
297
321
|
| `40` | Other HTTP 4xx |
|
|
298
322
|
| `50` | HTTP 5xx |
|
|
299
323
|
| `60` | Transport or connection failure |
|
|
@@ -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. |
|