@hagicode/cli 0.1.0-dev.42.1.b13f8d2 → 0.1.0-dev.47.1.f8624d4

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-CGAPe6tb.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-CAH7GrRu.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-CGAPe6tb.js");exports.createCliApp=e.createCliApp;exports.runCli=e.runCli;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./chunks/main-CAH7GrRu.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.42.1.b13f8d2",
3
+ "version": "0.1.0-dev.47.1.f8624d4",
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": {
@@ -17,7 +17,8 @@
17
17
  "files": [
18
18
  "dist",
19
19
  "README.md",
20
- "skills"
20
+ "skills",
21
+ "resources/saga"
21
22
  ],
22
23
  "packageManager": "pnpm@12.16.1",
23
24
  "engines": {
@@ -25,7 +26,7 @@
25
26
  },
26
27
  "scripts": {
27
28
  "clean": "rimraf dist",
28
- "build": "export NODE_OPTIONS=\"${NODE_OPTIONS:-} --max-old-space-size=8192\"; pnpm run clean && vite build",
29
+ "build": "export NODE_OPTIONS=\"${NODE_OPTIONS:-} --max-old-space-size=8192\"; pnpm run clean && vite build && node -e \"require('node:fs').chmodSync('dist/cli.js', 0o755)\"",
29
30
  "dev": "tsx ./src/cli.ts",
30
31
  "generate:grpc": "node ./buildTools/generate-grpc.mjs",
31
32
  "generate:grpc:check": "node ./buildTools/generate-grpc.mjs --check",
@@ -0,0 +1 @@
1
+ */runtime/
@@ -0,0 +1,12 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "files": {
4
+ ".gitignore": "49f38d5fe88ca07cdf13680b2d4f8a8f55d9b871e88192ad25db08e5ec1476fa",
5
+ "prompts/acceptance.md": "11ca06794d0ec19e87d1a4e883a67014563e9750b9fa62a8bedd43a69d52ca65",
6
+ "prompts/amend.md": "be621d1221a85656851548aee1e9e9d78480288bd856baf1bdbf50cb3317f49b",
7
+ "prompts/goal.md": "a545b01c9f6010df661f02cf62ef9bcae527ccf329f304ea82ffdda239b118df",
8
+ "prompts/plan.md": "bcc4c0509f2971083775db2b3678f41c55f16e69deba2d994d3ed636f314d917",
9
+ "prompts/research.md": "2504283ea845440c7909ff1da20bfb70f846d55ca16565740168848cfb8060b1",
10
+ "prompts/run.md": "1193fbc45645b513bbd6e1017513b62a53286d1d60fd00df479cff15de7c785b"
11
+ }
12
+ }
@@ -0,0 +1,15 @@
1
+ # acceptance.md
2
+
3
+ ## Instruction
4
+ Give every plan item one acceptance command. A separate verification worker runs it, so it must decide pass or fail by itself.
5
+
6
+ - One heading per item: `## <ID> <name>`, followed by one line `cmd: <command>`.
7
+ - The command runs from the project root (the folder that holds `.hagicode`), not from the saga root. Write `cd <dir> && ...` when it needs a subfolder. Exit code 0 means the item is done.
8
+ - The command must be able to fail. A command that always passes proves nothing.
9
+ - Every plan item has exactly one heading here, and every heading here is a plan item. If you change the command of an item that is already ticked, the next freeze reopens that item.
10
+
11
+ ## Template
12
+ ```
13
+ ## <ID> <name>
14
+ cmd: <command to run from the project root>
15
+ ```
@@ -0,0 +1,10 @@
1
+ # Folding an amendment into a saga
2
+
3
+ An amendment is a file under `<saga>/amendments/`. It is open while its `merged-in:` line is empty. Folding it in changes the planning artifacts only.
4
+
5
+ 1. `hagi saga status <saga> --path <root> --json` reports the current `revision` N (0 means the saga was never frozen). The merge target is `r<N+1>`.
6
+ 2. Read the amendment. Update `goal.md`, `research.md`, `acceptance.md` and `progress.md` so they agree with each other and with the amendment.
7
+ 3. Items may be added or superseded (`- ~ <ID> <name> | superseded in r<N+1>: <reason>`), never deleted. Do not touch the ticks: when an item's acceptance command changes, the next freeze reopens it.
8
+ 4. Do not decide new open questions yourself. List them under `## Open questions`.
9
+ 5. In the amendment file set `merged-in: r<N+1>`.
10
+ 6. Run `hagi saga check <saga> --phase plan --path <root>` and fix what it reports. Do not run `freeze`.
@@ -0,0 +1,23 @@
1
+ # goal.md
2
+
3
+ ## Instruction
4
+ Write the goal contract of the saga: what it must achieve, in what scope, with which safety rules.
5
+
6
+ - Every work item the saga will have must be checkable by a machine (a command that exits 0 when the item is done). Say so in the goal.
7
+ - Write the non-goals and the block conditions (when an item is blocked instead of forced).
8
+ - Do not write implementation details.
9
+ - Write in the language the user used for the objective.
10
+ - If `amendments/` holds files whose `merged-in:` line is empty, fold them in and set `merged-in: r<next revision>` in each file (see `prompts/amend.md`).
11
+
12
+ ## Template
13
+ ```
14
+ ## Goal
15
+
16
+ ## Scope
17
+
18
+ ## Non-goals
19
+
20
+ ## Safety rules
21
+
22
+ ## Block conditions
23
+ ```
@@ -0,0 +1,16 @@
1
+ # progress.md (the plan)
2
+
3
+ ## Instruction
4
+ List the work items, one line each: `- [ ] <ID> <name> | `. The evidence path is added when an item is ticked.
5
+
6
+ - Items are small enough to verify with one command, mostly independent, and ordered so that earlier items do not depend on later ones.
7
+ - Keep the list short enough to review (roughly 3 to 15 items). Use the user's work items as a start; add or split items only when needed.
8
+ - Do not add items for committing, pushing, opening a pull request, releasing or archiving: they are outside the contract.
9
+ - IDs match the headings of `acceptance.md`.
10
+ - In a revision an item may be added or superseded, never deleted. A superseded item is written without brackets: `- ~ <ID> <name> | superseded in r<N>: <reason>`.
11
+ - Planning ends here. Do not run `freeze`, do not start the run, do not edit project code.
12
+
13
+ ## Template
14
+ ```
15
+ - [ ] <ID> <name> |
16
+ ```
@@ -0,0 +1,16 @@
1
+ # research.md
2
+
3
+ ## Instruction
4
+ Read the project and the references the user gave. Read only: change no project file.
5
+
6
+ - Record what you found: the layout, the existing conventions, the commands that run the tests, anything the plan depends on.
7
+ - List every decision you cannot make yourself under the heading `## Open questions` (the Chinese heading `## 待决问题` also works). One line per question, starting with `- `. Leave them undecided: a human answers them before the run by rewriting a line as `- [decided: <answer>] <question>` (or `- [已决定: <结论>] <问题>`).
8
+ - If there is nothing to decide, write `- none` under that heading.
9
+ - If an item has no honest verification command, say so here as an open question. Do not invent a check.
10
+
11
+ ## Template
12
+ ```
13
+ ## Findings
14
+
15
+ ## Open questions
16
+ ```
@@ -0,0 +1,18 @@
1
+ # Running a saga (the orchestrator)
2
+
3
+ You are the orchestrator. You dispatch work and keep the record. You do not plan, you do not edit the contract, and you do not implement items yourself. A human started this run; starting it is the approval.
4
+
5
+ `<root>` is the registered project path given in the run's input. Every command is `hagi saga <command> <saga> --path <root>` and works from any directory. Every command that changes a Saga also takes `--token <token>`: the token that `claim` printed.
6
+
7
+ 0. **Claim first.** `hagi saga claim <saga> --session <your session id> --path <root> --json`. Keep the printed token for every later command. If it refuses because another owner holds the Saga, stop and report it. Never take over by age, and never write a paused snapshot over another owner's progress.
8
+ 1. **Freeze.** `hagi saga freeze <saga> --path <root> --token <token>`. If it refuses (open questions, open amendments, a changed contract), run `hagi saga set-status <saga> --state paused --note "<reason>" --path <root> --token <token>`, stop and report the reasons. Do not edit contract files and do not decide open questions. A first freeze writes revision 1. After an amendment was merged it writes the next revision, chained to the previous one, and reopens the items whose acceptance command changed.
9
+ 2. **Check before every item and at every heartbeat.** `hagi saga assert-owner <saga> --path <root> --token <token>` before each dispatch, then `hagi saga check <saga> --phase run --path <root>`. If a check fails, pause the same way and report. An open amendment also means: pause.
10
+ 3. **Work one item at a time** (`hagi saga items <saga> --path <root>` lists them with their commands), unless the goal allows parallel items. For each item:
11
+ - An implementation worker writes code and tests only. Give it the goal, the item, the scope and the safety rules, the project root and its report path.
12
+ - A separate verification worker runs the item's command from `acceptance.md` in the project root, writes the output to `<root>/.hagicode/sagas/<saga>/runtime/evidence/<ITEM>.log`, and writes `<root>/.hagicode/sagas/<saga>/runtime/reports/<ITEM>.md` with the line `Status: PASS` (or `状态: PASS`) only if the exit code is 0. It changes no files.
13
+ - Only `hagi saga tick <saga> <ITEM> --path <root> --token <token>` ticks an item. It refuses without a PASS report and an evidence file.
14
+ 4. **On a failed verification, classify it.** An environment fault (missing module, port in use, quota) does not count. A real failure counts: give a tuning worker the failure log path, then verify again. After 3 real failures run `hagi saga block <saga> <ITEM> "<reason>" --path <root> --token <token>` and move on.
15
+ 5. **After a revision reopened items,** also re-verify any item whose command is the full regression: its own command did not change, so it was not reopened.
16
+ 6. **Keep the record.** Append a line to `<root>/.hagicode/sagas/<saga>/runtime/orchestrator-log.md` after every dispatch, verification and tick. Append every session you create to `<root>/.hagicode/sagas/<saga>/runtime/sessions.md`. Run `hagi saga set-status <saga> --state running --note "<heartbeat>" --path <root> --token <token>` at every heartbeat, and `hagi saga status <saga> --path <root> --json` to read the projection at the end.
17
+ 7. **Never leave a command hanging.** Every shell command must end on its own: never read from standard input, and give waits a limit. If something does not answer, report it instead of waiting forever.
18
+ 8. **Stop** when every item is ticked or blocked, when a pause condition appears, or when the human's run options say so. Write the terminal snapshot first (`set-status --state done` only when every active item is verified, otherwise `paused` with the reason), then, once every worker has stopped, `hagi saga release <saga> --path <root> --token <token> --workers-settled`. Do not archive and do not commit. Report the revision, every item with its state and evidence path, the blocked items with reasons, the sessions you created, and the next step for a human.
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
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: hagi
3
- description: Use when the user wants to run, verify, or update the HagiCode CLI against the Hagi API, or asks about their own projects, proposals, chat sessions, vaults, scheduled plans, or installed skills, including user skills versus built-in skills, and enabling or disabling a built-in skill. Also covers the OpenSpec workflow behind proposal commands and Super Commit, which commits a code repository through a HagiTask session. Keep package-first command guidance primary, then follow the proposal flow, local development, and validation references as needed. Inside a HagiCode session, read the session-data reference first for questions about the user's own data.
3
+ description: Use when the user wants to run, verify, or update the HagiCode CLI against the Hagi API, use local Saga initialization, creation, checks, writes, or read-only queries, or asks about their own projects, proposals, chat sessions, vaults, scheduled plans, or installed skills, including user skills versus built-in skills and enabling or disabling a built-in skill. Also covers the OpenSpec workflow behind proposal commands and Super Commit, which commits a code repository through a HagiTask session. Keep package-first command guidance primary, then follow the Saga, proposal, local development, and validation references as needed. Inside a HagiCode session, read the session-data reference first for questions about the user's own data.
4
4
  ---
5
5
 
6
6
  # Hagi
@@ -11,8 +11,11 @@ Use this skill when the user asks to:
11
11
  - list which installed skills are user skills and which are built-in skills, or which built-in skills are enabled
12
12
  - enable or disable a built-in skill
13
13
  - run or verify the `hagi` CLI
14
- - explain or execute `project`, `proposal`, `chat`, `hagitask`, `vault`, `plan`, or `skills` commands
14
+ - run, explain, or verify local `saga init`, `saga new`, `saga list`, `saga status`, or `saga items` operations
15
+ - run, explain, or verify local Saga ownership, checks, and writes: `claim`, `assert-owner`, `release`, `freeze`, `check`, `tick`, `block`, `set-status`, `amend`, `archive`
16
+ - explain or execute `project`, `proposal`, `chat`, `hagitask`, `vault`, `plan`, `skills`, or `saga` commands
15
17
  - explain the OpenSpec workflow behind `proposal` commands, which stage each command starts, or how to follow a run until it settles
18
+ - take turns on a shared resource (a game server, a test environment) with other agents through `hagi agent lock`
16
19
  - run, explain or check a Super Commit, which commits the uncommitted changes of a code repository through a HagiTask session
17
20
  - inspect CLI flags, environment variables, exit codes, or JSON automation behavior
18
21
  - validate proposal-session flows against a backend API
@@ -37,6 +40,8 @@ Use this skill when the user asks to:
37
40
  5. Source-tree development in `repos/cli` -> read [`references/local-development.md`](references/local-development.md)
38
41
  6. Post-change validation -> read [`references/validation.md`](references/validation.md)
39
42
  7. The user's own data, inside a HagiCode session -> read [`references/session-data.md`](references/session-data.md)
43
+ 8. Local Saga initialization, creation, list, status, item queries, ownership, checks, freeze, ticks, status writes, amendments, or archive -> read [`references/saga.md`](references/saga.md)
44
+ 9. A shared resource that other agents also use, or any `hagi agent lock` command -> read [`references/resource-lock.md`](references/resource-lock.md)
40
45
 
41
46
  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
47
 
@@ -81,6 +86,10 @@ The current top-level command families are:
81
86
  - `hagitask`
82
87
  - `plan`
83
88
  - `skills`
89
+ - `agent`
90
+ - `saga`
91
+
92
+ `hagi saga` provides `init`, `new`, `list`, `status`, and `items`, plus the write and check commands `claim`, `assert-owner`, `release`, `freeze`, `check`, `tick`, `block`, `set-status`, `amend`, and `archive`; mutating commands take `--token` or `SAGA_EXECUTION_TOKEN`. With `--path`, these operate on host-local files without reading backend configuration or calling the API; `--project-id` resolves a project through the backend. See [`references/saga.md`](references/saga.md) for initialization, creation, offline behavior, and read-only query guarantees.
84
93
 
85
94
  `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
95
 
@@ -90,6 +99,16 @@ HagiTask sessions (`hagi hagitask`) run a preset task with a structured input. `
90
99
 
91
100
  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
101
 
102
+ ## Resource lock
103
+
104
+ `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:
105
+
106
+ - 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`.
107
+ - 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.
108
+ - 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.
109
+ - 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.
110
+ - 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.
111
+
93
112
  ## Source of truth
94
113
 
95
114
  - Canonical writable skill source: `repos/cli/skills/hagi/`
@@ -102,5 +121,7 @@ Each family also reads one session by id. `proposal get` and `hagitask get` read
102
121
  - [`references/proposal-flow.md`](references/proposal-flow.md) - explicit proposal lifecycle sequence and command examples
103
122
  - [`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
123
  - [`references/super-commit.md`](references/super-commit.md) - Super Commit: starting it by prompt id, its options, result reading and the explicit-request rule
124
+ - [`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
125
+ - [`references/saga.md`](references/saga.md) - local initialization, creation, ownership, checks, Saga writes, and read-only queries
105
126
  - [`references/local-development.md`](references/local-development.md) - maintainer-only development, build, and repository internals
106
127
  - [`references/validation.md`](references/validation.md) - targeted verification after documentation or code changes
@@ -1,7 +1,7 @@
1
1
  interface:
2
2
  display_name: "Hagi CLI"
3
- short_description: "Answer questions about your HagiCode data and skills, operate the hagi CLI, follow the OpenSpec workflow, and run a Super Commit."
4
- default_prompt: "Use $hagi to look up your projects, proposals, sessions, vaults, plans, or skills, to follow the OpenSpec workflow of a proposal, to run a Super Commit, to enable or disable a built-in skill, or to inspect, run, or update the HagiCode CLI in repos/cli."
3
+ short_description: "Operate the hagi CLI, including local Saga commands, and work with HagiCode data, skills, proposals, and Super Commit."
4
+ default_prompt: "Use $hagi to operate local Saga init, new, list, status, or items commands and the Saga ownership, check, freeze, tick, set-status, amend, and archive commands; look up HagiCode projects, proposals, sessions, vaults, plans, or skills; follow a proposal's OpenSpec workflow; run a Super Commit; enable or disable a built-in skill; or inspect, run, or update the CLI in repos/cli."
5
5
 
6
6
  policy:
7
7
  allow_implicit_invocation: true
@@ -35,6 +35,7 @@ For one-off execution, replace `hagi` with `npx @hagicode/cli`.
35
35
  hagi --help
36
36
  hagi project list --json
37
37
  hagi proposal list --json
38
+ hagi saga list --path .
38
39
  hagi chat list --json
39
40
  hagi hagitask list --json
40
41
  hagi hagitask create --title "Auto commit" --project-id <project-id> --prompt-id auto-compose-commit.en-US
@@ -55,9 +56,11 @@ Example:
55
56
  hagi --base-url https://api.example.com --token "$HAGI_TOKEN" project list --json
56
57
  ```
57
58
 
59
+ Saga commands using `--path` accept these flags but do not read backend configuration or contact the backend.
60
+
58
61
  ## Environment variables
59
62
 
60
- The CLI resolves runtime settings centrally before every command.
63
+ Backend operations resolve runtime settings centrally before accessing the API.
61
64
  Explicit flags always override environment variables.
62
65
 
63
66
  | Variable | Purpose |
@@ -78,7 +81,9 @@ The base URL can also be stored in a local config file:
78
81
  The file is `$XDG_CONFIG_HOME/hagi/config.json`, or `~/.config/hagi/config.json` when `XDG_CONFIG_HOME` is unset. The `steer` key must be `true` or `false`; any other value exits with code `2` and names the file.
79
82
 
80
83
  Base URL precedence, highest first: `--base-url`, then `HAGI_API_BASE_URL`, then `baseUrl` in the config file.
81
- There is no default. When none of the three is set, the CLI exits with code `2` and names all three sources.
84
+ There is no default. When none of the three is set, a backend operation exits with code `2` and names all three sources.
85
+ The local Saga selector `--path` is the exception: it does not read this file, so malformed, unreachable, or absent backend
86
+ settings do not affect that local operation.
82
87
 
83
88
  ## Command overview
84
89
 
@@ -87,16 +92,45 @@ hagi
87
92
  |-- project list | repositories | create | update | delete
88
93
  | `-- monospecs show | init | set | validate | reload | clone | remove-checkout
89
94
  |-- proposal list | create | optimize-description | generate | annotate | execute | archive | status | complete | send | get | messages
95
+ |-- saga init | new | list | status | items | claim | assert-owner | release | freeze | check | tick | block | set-status | amend | archive
90
96
  |-- vault list | create | update | delete
91
97
  | `-- files list | preview
92
98
  |-- chat list | create | archive | delete | send
93
99
  |-- hagitask list | schema | create | send | get | messages
94
100
  |-- 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
101
+ |-- skills local list | enable | disable | uninstall
102
+ | gallery search | detail | install
103
+ | trusted list | upsert | delete | reset
104
+ `-- agent lock acquire | status | renew | heartbeat | release | requisition | list
105
+ ```
106
+
107
+ ## Saga commands
108
+
109
+ Each command requires exactly one nonempty project selector:
110
+
111
+ - `--path <directory>` resolves a local directory on the CLI host. Relative paths use the current working directory. This
112
+ selector does not read backend configuration or make API calls, and shared backend flags do not change its behavior.
113
+ - `--project-id <id>` reads the project list through the configured backend. Its project path must be a directory available
114
+ on the machine running the CLI; the CLI does not execute filesystem operations remotely.
115
+
116
+ Missing, empty, or conflicting selectors exit `2` before backend access or file writes. Paths with spaces should be quoted.
117
+ Shared `--base-url`, `--token`, `--lang`, and `--json` options are accepted.
118
+
119
+ ```bash
120
+ hagi saga init --path "./My Project"
121
+ hagi saga new demo --path "./My Project" --json
122
+ hagi saga list --path "./My Project"
123
+ hagi saga status demo --project-id <project-id> --json
124
+ hagi saga items demo --path "./My Project" --json
125
+ hagi saga claim demo --path "./My Project" --session orchestrator-1 --json
126
+ hagi saga check demo --path "./My Project" --phase run --json
127
+ hagi saga tick demo T1 --path "./My Project" --token "$SAGA_EXECUTION_TOKEN" --json
98
128
  ```
99
129
 
130
+ For offline use, invoke an installed package or a package already present in the `npx` cache. An uncached
131
+ `npx @hagicode/cli` command may need network access to download the package; the local `--path` operation itself does not
132
+ need backend access.
133
+
100
134
  ## Project repositories
101
135
 
102
136
  `project repositories --id <project-id>` is read-only. It reads the same aggregation snapshot as `project list` and prints one project's repositories: name, path relative to the project root (for example `repos/cli`), display name, type and branch, with the project's MonoSpecs flag. The `path` values are the ones a proposal's `Repository scope` names.
@@ -280,6 +314,28 @@ hagi skills local disable --skill-name hagi --json
280
314
  - 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
315
  - `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
316
 
317
+ ## Agent lock commands
318
+
319
+ `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`.
320
+
321
+ ```bash
322
+ hagi agent lock acquire --lock-id eu-1/duckov --owner sess-7f2c
323
+ hagi agent lock acquire --server eu-1 --game duckov --owner sess-7f2c --wait --timeout 900
324
+ hagi agent lock status --lock-id eu-1/duckov --owner sess-7f2c --json
325
+ hagi agent lock heartbeat --lock-id eu-1/duckov --owner sess-7f2c --epoch 7
326
+ hagi agent lock renew --lock-id eu-1/duckov --owner sess-7f2c --epoch 7
327
+ hagi agent lock release --lock-id eu-1/duckov --owner sess-7f2c --epoch 7
328
+ hagi agent lock requisition --lock-id eu-1/duckov --owner sess-7f2c --reason "User order: take the lock now"
329
+ hagi agent lock list --prefix eu-1/
330
+ ```
331
+
332
+ - 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`.
333
+ - `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.
334
+ - `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>`.
335
+ - `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.
336
+ - Timing: 10-minute feedback timeout, 24-hour maximum hold, 2-minute heartbeat interval, 30-second claim window, 60-second waiter TTL.
337
+ - Never run `requisition` unless the user explicitly ordered a forced take.
338
+
283
339
  ## Exit codes
284
340
 
285
341
  Stable non-zero exits used by the CLI. The HTTP numbers are the equivalents of the gRPC status the backend answered with:
@@ -293,7 +349,8 @@ Stable non-zero exits used by the CLI. The HTTP numbers are the equivalents of t
293
349
  | `24` | HTTP 404 |
294
350
  | `29` | HTTP 409 |
295
351
  | `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 `hagitask get` reached `--timeout` before the session settled. The last status is printed. Not a success, and not a backend error |
352
+ | `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 |
353
+ | `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
354
  | `40` | Other HTTP 4xx |
298
355
  | `50` | HTTP 5xx |
299
356
  | `60` | Transport or connection failure |
@@ -63,6 +63,16 @@ The DTO types in `src/generated/api/models` are committed and maintained by hand
63
63
  generated: the backend's OpenAPI document now lists only the retained REST routes, so regenerating from it would delete the
64
64
  types of every removed route. When a proto DTO changes, edit or add the matching type there.
65
65
 
66
+ ## Saga asset bundle
67
+
68
+ The Saga resources are owned by this repository. `resources/saga/` contains the six method prompt files, `.gitignore`,
69
+ and `manifest.json`; there is no standalone Saga tool and no sync step. Keep the bytes raw: do not render templates or
70
+ normalize line endings. The manifest uses schema version `1` and sorted POSIX relative paths mapped to lowercase SHA-256
71
+ digests; it does not hash itself.
72
+
73
+ After editing a prompt, update its digest in `manifest.json`. `tests/runtime/saga/assets.test.ts` fails on any mismatch
74
+ between the files and the manifest, and `pnpm run pack:check` verifies the published contents.
75
+
66
76
  ## Release automation
67
77
 
68
78
  GitHub workflows under `.github/workflows/` handle release drafting and npm publishing.
@@ -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. |