@zenera/cli 1.1.23 → 1.1.25

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.
Files changed (184) hide show
  1. package/README.md +88 -2
  2. package/dist/ask.d.ts +51 -0
  3. package/dist/ask.js +166 -0
  4. package/dist/banner.js +2 -2
  5. package/dist/batch.d.ts +83 -0
  6. package/dist/batch.js +413 -0
  7. package/dist/catalog.d.ts +11 -1
  8. package/dist/catalog.js +80 -7
  9. package/dist/command.d.ts +6 -0
  10. package/dist/commands/inspect.d.ts +57 -0
  11. package/dist/commands/inspect.js +539 -91
  12. package/dist/commands/list.js +128 -19
  13. package/dist/commands/memory.js +461 -32
  14. package/dist/commands/meta.js +311 -21
  15. package/dist/commands/models.js +110 -14
  16. package/dist/commands/open.js +4 -4
  17. package/dist/commands/run.js +175 -36
  18. package/dist/commands/sandbox.js +148 -15
  19. package/dist/engine.d.ts +24 -0
  20. package/dist/engine.js +61 -8
  21. package/dist/eventlog.d.ts +59 -0
  22. package/dist/eventlog.js +146 -0
  23. package/dist/liveness.d.ts +7 -0
  24. package/dist/liveness.js +20 -3
  25. package/dist/main.js +5 -2
  26. package/dist/meta/editor.d.ts +9 -0
  27. package/dist/meta/editor.js +60 -0
  28. package/dist/meta/finetune/children.d.ts +8 -0
  29. package/dist/meta/finetune/children.js +76 -0
  30. package/dist/meta/finetune/command.d.ts +30 -0
  31. package/dist/meta/finetune/command.js +338 -0
  32. package/dist/meta/finetune/dataset/anchors.d.ts +41 -0
  33. package/dist/meta/finetune/dataset/anchors.js +239 -0
  34. package/dist/meta/finetune/dataset/changes.d.ts +36 -0
  35. package/dist/meta/finetune/dataset/changes.js +319 -0
  36. package/dist/meta/finetune/dataset/command.d.ts +17 -0
  37. package/dist/meta/finetune/dataset/command.js +1011 -0
  38. package/dist/meta/finetune/dataset/drift.d.ts +33 -0
  39. package/dist/meta/finetune/dataset/drift.js +71 -0
  40. package/dist/meta/finetune/dataset/export.d.ts +26 -0
  41. package/dist/meta/finetune/dataset/export.js +51 -0
  42. package/dist/meta/finetune/dataset/index.d.ts +2 -0
  43. package/dist/meta/finetune/dataset/index.js +2 -0
  44. package/dist/meta/finetune/dataset/sample.d.ts +50 -0
  45. package/dist/meta/finetune/dataset/sample.js +197 -0
  46. package/dist/meta/finetune/dataset/schema.d.ts +33 -0
  47. package/dist/meta/finetune/dataset/schema.js +216 -0
  48. package/dist/meta/finetune/dataset/store.d.ts +35 -0
  49. package/dist/meta/finetune/dataset/store.js +177 -0
  50. package/dist/meta/finetune/dataset/types.d.ts +125 -0
  51. package/dist/meta/finetune/dataset/types.js +39 -0
  52. package/dist/meta/finetune/failure.d.ts +15 -0
  53. package/dist/meta/finetune/failure.js +26 -0
  54. package/dist/meta/finetune/feedback.d.ts +45 -0
  55. package/dist/meta/finetune/feedback.js +85 -0
  56. package/dist/meta/finetune/improvements.d.ts +51 -0
  57. package/dist/meta/finetune/improvements.js +244 -0
  58. package/dist/meta/finetune/index.d.ts +2 -0
  59. package/dist/meta/finetune/index.js +2 -0
  60. package/dist/meta/finetune/journal.d.ts +35 -0
  61. package/dist/meta/finetune/journal.js +135 -0
  62. package/dist/meta/finetune/live.d.ts +74 -0
  63. package/dist/meta/finetune/live.js +226 -0
  64. package/dist/meta/finetune/loop.d.ts +3 -0
  65. package/dist/meta/finetune/loop.js +114 -0
  66. package/dist/meta/finetune/memories.d.ts +47 -0
  67. package/dist/meta/finetune/memories.js +154 -0
  68. package/dist/meta/finetune/report.d.ts +33 -0
  69. package/dist/meta/finetune/report.js +1284 -0
  70. package/dist/meta/finetune/retry.d.ts +24 -0
  71. package/dist/meta/finetune/retry.js +96 -0
  72. package/dist/meta/finetune/sampler.d.ts +18 -0
  73. package/dist/meta/finetune/sampler.js +41 -0
  74. package/dist/meta/finetune/system.d.ts +27 -0
  75. package/dist/meta/finetune/system.js +132 -0
  76. package/dist/meta/finetune/train.d.ts +39 -0
  77. package/dist/meta/finetune/train.js +391 -0
  78. package/dist/meta/finetune/tui.d.ts +16 -0
  79. package/dist/meta/finetune/tui.js +291 -0
  80. package/dist/meta/finetune/tuning.d.ts +167 -0
  81. package/dist/meta/finetune/tuning.js +289 -0
  82. package/dist/meta/finetune/usage.d.ts +62 -0
  83. package/dist/meta/finetune/usage.js +225 -0
  84. package/dist/meta/host.d.ts +14 -0
  85. package/dist/meta/host.js +20 -0
  86. package/dist/meta/index.d.ts +11 -0
  87. package/dist/meta/index.js +11 -0
  88. package/dist/meta/launch.d.ts +81 -0
  89. package/dist/meta/launch.js +286 -0
  90. package/dist/meta/model.d.ts +44 -0
  91. package/dist/meta/model.js +119 -0
  92. package/dist/meta/prompts.d.ts +18 -0
  93. package/dist/meta/prompts.js +61 -0
  94. package/dist/meta/relay.d.ts +20 -0
  95. package/dist/meta/relay.js +123 -0
  96. package/dist/meta/tokens.d.ts +52 -0
  97. package/dist/meta/tokens.js +226 -0
  98. package/dist/meta/window.d.ts +61 -0
  99. package/dist/meta/window.js +198 -0
  100. package/dist/meta/wire.d.ts +42 -0
  101. package/dist/meta/wire.js +129 -0
  102. package/dist/modelref.d.ts +16 -0
  103. package/dist/modelref.js +41 -0
  104. package/dist/narrate.js +5 -2
  105. package/dist/podman.d.ts +42 -0
  106. package/dist/podman.js +198 -5
  107. package/dist/progress.d.ts +33 -0
  108. package/dist/progress.js +402 -0
  109. package/dist/projects.d.ts +0 -5
  110. package/dist/projects.js +2 -15
  111. package/dist/request.d.ts +45 -0
  112. package/dist/request.js +300 -0
  113. package/dist/sandbox.d.ts +2 -0
  114. package/dist/sandbox.js +20 -3
  115. package/dist/scaffold.d.ts +15 -6
  116. package/dist/scaffold.js +5 -60
  117. package/dist/session.d.ts +26 -1
  118. package/dist/session.js +46 -32
  119. package/dist/term.d.ts +5 -0
  120. package/dist/term.js +28 -3
  121. package/dist/trace.d.ts +84 -0
  122. package/dist/trace.js +737 -0
  123. package/dist/tui/models.d.ts +33 -0
  124. package/dist/tui/models.js +389 -0
  125. package/dist/tui/wrap.d.ts +5 -0
  126. package/dist/tui/wrap.js +12 -0
  127. package/dist/usage.d.ts +78 -0
  128. package/dist/usage.js +51 -0
  129. package/dist/validate.js +25 -3
  130. package/package.json +3 -2
  131. package/templates/editor/.github/copilot-instructions.md +41 -107
  132. package/templates/editor/.github/prompts/analyze.prompt.md +31 -0
  133. package/templates/editor/.github/prompts/dataset.prompt.md +108 -0
  134. package/templates/editor/.github/prompts/finetune-apply.prompt.md +91 -0
  135. package/templates/editor/.github/prompts/finetune.prompt.md +744 -0
  136. package/templates/editor/.github/prompts/new-agent.prompt.md +3 -1
  137. package/templates/editor/.github/prompts/new-skill.prompt.md +2 -0
  138. package/templates/editor/.github/prompts/project-review.prompt.md +4 -3
  139. package/templates/editor/.github/skills/zen-analyze-run/SKILL.md +483 -0
  140. package/templates/editor/.github/skills/zen-cli/SKILL.md +18 -4
  141. package/templates/editor/.github/skills/zen-cli/references/check.md +7 -5
  142. package/templates/editor/.github/skills/zen-cli/references/dataset.md +34 -0
  143. package/templates/editor/.github/skills/zen-cli/references/finetune.md +77 -0
  144. package/templates/editor/.github/skills/zen-cli/references/inspect.md +300 -21
  145. package/templates/editor/.github/skills/zen-cli/references/memory.md +121 -28
  146. package/templates/editor/.github/skills/zen-cli/references/meta.md +61 -0
  147. package/templates/editor/.github/skills/zen-cli/references/models.md +6 -2
  148. package/templates/editor/.github/skills/zen-cli/references/projects.md +12 -5
  149. package/templates/editor/.github/skills/zen-cli/references/rag.md +24 -6
  150. package/templates/editor/.github/skills/zen-cli/references/run.md +201 -4
  151. package/templates/editor/.github/skills/zen-cli/references/sandbox.md +10 -1
  152. package/templates/editor/.github/skills/zen-code-python/SKILL.md +123 -0
  153. package/templates/editor/.github/skills/zen-code-python/references/code_python.md +48 -0
  154. package/templates/editor/.github/skills/zen-dataset/SKILL.md +253 -0
  155. package/templates/editor/.github/skills/zen-finetune/SKILL.md +1791 -0
  156. package/templates/editor/.github/skills/zen-finetune/references/readme-template.md +268 -0
  157. package/templates/editor/.github/skills/zen-finetune/scripts/batch.mjs +390 -0
  158. package/templates/editor/.github/skills/zen-finetune/scripts/difficult.mjs +243 -0
  159. package/templates/editor/.github/skills/zen-finetune/scripts/memory.mjs +280 -0
  160. package/templates/editor/.github/skills/zen-finetune/scripts/next.mjs +398 -0
  161. package/templates/editor/.github/skills/zen-finetune/scripts/report.mjs +797 -0
  162. package/templates/editor/.github/skills/zen-finetune/scripts/select.mjs +354 -0
  163. package/templates/editor/.github/skills/zen-finetune/scripts/usage.mjs +722 -0
  164. package/templates/editor/.github/skills/zen-inspect/SKILL.md +431 -0
  165. package/templates/editor/.github/skills/zen-inspect-ask/SKILL.md +270 -0
  166. package/templates/editor/.github/skills/zen-memory/SKILL.md +78 -29
  167. package/templates/editor/.github/skills/zen-memory-warmup/SKILL.md +363 -531
  168. package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +20 -8
  169. package/templates/editor/.github/skills/zen-review/SKILL.md +5 -3
  170. package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +5 -4
  171. package/templates/editor/.github/skills/zen-sandbox/SKILL.md +25 -2
  172. package/templates/editor/.github/skills/zen-sandbox-capacity/SKILL.md +334 -0
  173. package/templates/editor/.github/skills/zen-sandbox-capacity/references/loadtest.md +181 -0
  174. package/templates/editor/.github/skills/zen-sandbox-capacity/scripts/preflight_sandbox.sh +457 -0
  175. package/templates/editor/.github/skills/zen-topology/SKILL.md +283 -0
  176. package/templates/editor/.vscode/agents.schema.json +1 -1
  177. package/templates/editor/.vscode/settings.json +2 -0
  178. package/templates/project/agents/files-instructions.md +35 -13
  179. package/templates/project/agents/fork-instructions.md +27 -0
  180. package/templates/project/agents/memory-instructions.md +106 -34
  181. package/templates/project/agents/tools-instructions.md +7 -4
  182. package/templates/project/gitignore +5 -0
  183. package/dist/meta.d.ts +0 -221
  184. package/dist/meta.js +0 -684
@@ -0,0 +1,61 @@
1
+ # `zen meta` - the meta agent, on this project
2
+
3
+ ```
4
+ zen meta run [project] "<question>" ask it something
5
+ zen meta run [project] /<name> [words] run .github/prompts/<name>.prompt.md
6
+ zen meta prompts [project] list the stored prompts
7
+ zen meta resume [project] [session] carry on a run that stopped
8
+ zen meta model [ref] show or set the model it uses
9
+ zen meta dataset <verb> see dataset.md
10
+ ```
11
+
12
+ `zen meta --help` has every option.
13
+
14
+ ## `--json`
15
+
16
+ `zen meta run --json` prints ONE object on stdout when the run ends:
17
+
18
+ ```json
19
+ {
20
+ "project": "/path/to/project",
21
+ "provider": "vertex",
22
+ "model": "vertex/gemini-3.8-flash",
23
+ "sessionId": "…",
24
+ "exitCode": 0,
25
+ "resumes": 0,
26
+ "usage": {},
27
+ "tokens": {
28
+ "calls": 12,
29
+ "inputTokens": 0,
30
+ "cachedInputTokens": 0,
31
+ "outputTokens": 0,
32
+ "reasoningTokens": 0
33
+ },
34
+ "answer": "the final answer, as markdown",
35
+ "answerFile": "/path/to/project/.tmp/logs/meta.<when>.md"
36
+ }
37
+ ```
38
+
39
+ `tokens` is summed over every model call of the run; `usage` is the meta
40
+ agent's own end-of-session summary, passed through as it came, and may be absent.
41
+
42
+ - Progress goes to stderr as plain lines: no box, no animation, no status row.
43
+ stdout carries the object and nothing else, so redirect it to keep it:
44
+
45
+ ```
46
+ zen meta run acme /analyze --json > analyze.json
47
+ zen meta run acme /analyze --json > analyze.json 2> analyze.log
48
+ ```
49
+
50
+ - Nothing is asked. A `run` with no question, no `/<name>` and nothing piped
51
+ in is a usage error (exit 2) that lists the prompts, not a menu.
52
+ - A run the meta agent ends in failure still prints the object, then exits 1.
53
+ Check `exitCode`; `sessionId` is what `zen meta resume` takes. A run that
54
+ never starts (no key, bad model) prints no object, only the error.
55
+ - `--dry-run --json` gives `{project, provider, model, from, command, env}`,
56
+ secrets masked, and runs nothing.
57
+ - `zen meta prompts --json` gives `[{name, description, path}]`;
58
+ `zen meta model --json` gives `{model, from, sources}`.
59
+
60
+ The answer is also saved as markdown on every run, with or without `--json`;
61
+ `answerFile` is its path.
@@ -1,7 +1,7 @@
1
1
  # Models - `zen models`
2
2
 
3
3
  ```
4
- zen models <providers|ls|search|show|test|pick> [ref] [options]
4
+ zen models <providers|ls|search|show|test|pick|browse> [ref] [options]
5
5
  ```
6
6
 
7
7
  Alias: `model`.
@@ -21,6 +21,7 @@ but a credential.
21
21
  | `zen models show <ref>` | One model, every field the vendor gave |
22
22
  | `zen models test <ref> …` | One real minimal call per ref, and a verdict |
23
23
  | `zen models pick` | The first ref that answers, printed on stdout |
24
+ | `zen models browse` | Interactive TUI for a person; needs a terminal |
24
25
 
25
26
  Filters, on `ls` and `search`: `--chat`, `--embeddings`, `--images`, `--audio`,
26
27
  `--tools`, `--vision`, `--free`, `--min-context <n>`, `--provider <name>`,
@@ -28,6 +29,9 @@ Filters, on `ls` and `search`: `--chat`, `--embeddings`, `--images`, `--audio`,
28
29
 
29
30
  `pick` requires `--chat` or `--embedding`, and takes `--provider` and `--limit`.
30
31
 
32
+ `browse` is for a person at a terminal and refuses without one (exit 2). An
33
+ agent wants `ls --json`, `search` or `pick` instead.
34
+
31
35
  ## Where the lists come from
32
36
 
33
37
  The providers themselves, cached for a day in `~/.zenera/neo/catalog`. When a
@@ -71,7 +75,7 @@ This is what the command is for.
71
75
  ```
72
76
  $ zen models test vertex:gemini-embedding-001
73
77
  vertex:gemini-embedding-001 blocked Vertex AI API has not been used in project my-proj …
74
- vertex:gemini-embedding-001: gcloud services enable aiplatform.googleapis.com --project my-proj
78
+ fix vertex:gemini-embedding-001 gcloud services enable aiplatform.googleapis.com --project my-proj
75
79
  error 1 of 1 did not answer
76
80
  find one that does: zen models pick --embedding
77
81
  ```
@@ -81,16 +81,23 @@ the first segment as a **provider name**, not a vendor, so a bare
81
81
  ## `zen list`
82
82
 
83
83
  ```
84
- zen list [--sessions] [--prune]
84
+ zen list [--sessions] [--runs] [--limit <n>] [--prune]
85
85
  ```
86
86
 
87
87
  Every known project: its sessions, the last run, and whether one is live right
88
88
  now.
89
89
 
90
- | Flag | Meaning |
91
- | ------------ | -------------------------------------------- |
92
- | `--sessions` | Expand each project into its sessions |
93
- | `--prune` | Forget entries whose directory has gone away |
90
+ | Flag | Meaning |
91
+ | ------------- | -------------------------------------------------------- |
92
+ | `--sessions` | Expand each project into its sessions |
93
+ | `--runs` | The most recent runs, newest first, across every project |
94
+ | `--limit <n>` | Runs to list. Default 20 |
95
+ | `--prune` | Forget entries whose directory has gone away |
96
+
97
+ `--runs` answers "what ran last", which is not a question about one project, so
98
+ on its own it replaces the table rather than following it; each row names the
99
+ project and session it belongs to, and `--json` gives the run directory too —
100
+ which is what `zen inspect --dir` takes.
94
101
 
95
102
  The registry is an index, not the truth. An entry pointing at a directory that
96
103
  no longer exists is shown dimmed rather than hidden, because a moved project is
@@ -558,6 +558,7 @@ ordinary hybrid search and narrow by file or section.
558
558
  zen rag docs list <files|sections|tables> [-f <pattern>] [-s <section>]
559
559
  zen rag docs grep <pattern> [-f <pattern>] [--regex] [--case-sensitive]
560
560
  zen rag docs show <file> [--section <name>] [--lines <from-to>]
561
+ zen rag docs show <target>... [--max-lines <n>]
561
562
  ```
562
563
 
563
564
  `list sections` gives every heading with the lines it spans, which is how a
@@ -566,6 +567,23 @@ total even when `--limit` cuts the rows, so unlike a search it can answer
566
567
  whether a string appears at all. `show` prints a document, a named section, or
567
568
  a line range, verbatim.
568
569
 
570
+ ### Reading several passages in one call
571
+
572
+ Name every passage you will read or cite in **one** `show`, as
573
+ `<document>:<a>-<b>`, `<document>:<a>` or `<document>#<heading>` - the form a
574
+ citation is written in, so a citation pastes back unchanged:
575
+
576
+ ```sh
577
+ zen rag docs show -d <index-dir> "<doc-1>:24-60" "<doc-2>:25-40" "<doc-3>#<heading>"
578
+ ```
579
+
580
+ Each target prints under a header that is itself a target, in the order given;
581
+ overlapping ranges in one document print once. A missing document or heading is
582
+ reported in place and the rest still print (exit 1 only when none could be
583
+ read). `--max-lines` (default 400) is shared fairly between targets, and a cut
584
+ block ends with the target that continues it. `--json` returns
585
+ `{results, printed, failed}`. `--section` and `--lines` read one document only.
586
+
569
587
  ### Reading a complete section or table
570
588
 
571
589
  Search finds relevant passages, not necessarily every row of a table. When the
@@ -596,12 +614,12 @@ the answer needs every row.
596
614
 
597
615
  `@zenera/rag/docs/tools` exports four tools in the group `docs`:
598
616
 
599
- | Tool | For |
600
- | ------------- | --------------------------------------------------------------- |
601
- | `search_docs` | The search above, with the same narrowing parameters |
602
- | `list_docs` | The documents, their headings, or their tables - no ranking |
603
- | `grep_docs` | Every matching line, counted in full - the way to prove absence |
604
- | `read_docs` | A section or a line range, verbatim and complete |
617
+ | Tool | For |
618
+ | ------------- | ---------------------------------------------------------------- |
619
+ | `search_docs` | The search above, with the same narrowing parameters |
620
+ | `list_docs` | The documents, their headings, or their tables - no ranking |
621
+ | `grep_docs` | Every matching line, counted in full - the way to prove absence |
622
+ | `read_docs` | A section or a line range, verbatim; several at once via `reads` |
605
623
 
606
624
  Only `search_docs` ranks. Every answer carries line numbers and `read_docs`
607
625
  takes them, which is the loop the subject exists for: find the passage, read
@@ -14,14 +14,18 @@ answers once on stdout and exits.
14
14
  | `--project <name\|dir>` | Which project. Inferred from the directory otherwise |
15
15
  | `--session <id>` | Continue a particular session |
16
16
  | `--new` | Start a new session rather than continuing one |
17
+ | `--input <file>` | Read the whole request from JSON; `-` is stdin |
17
18
  | `--workspace <dir>` | What the agent may read and write |
18
19
  | `--memory <dir>` | Where the agents remember into, instead of the project's |
20
+ | `--memory-read-only` | Recall from it; write nothing back |
19
21
  | `--model <ref>` | Override the default model for this run |
20
22
  | `--image <ref>` | Override the container image commands run in |
21
23
  | `--read-only` | Withhold every tool that can write |
22
24
  | `--plain` | Never open the TUI; a prompt is then required |
23
25
  | `--theme <dark\|light>` | Palette for the TUI; `auto` detects. `$ZENERA_THEME` |
24
26
  | `--out <file>` | Put the answer in this file instead of on stdout |
27
+ | `--batch-dir <dir>` | Where a batch's runs live. `<project>/batches/<stamp>` |
28
+ | `--concurrency <n>` | How many of a batch run at once. Default 16, most 32 |
25
29
  | `--yes` | Say yes to the workspace and install questions |
26
30
 
27
31
  Flags always beat the file: the repository states intent, the invocation
@@ -38,6 +42,7 @@ the file is the only place it lands.
38
42
  zen run "what changed?" > out.md answer in the file, progress on screen
39
43
  zen run "what changed?" > out.md 2>/dev/null ... and nothing on screen at all
40
44
  zen run --out out.md "what changed?" the same file, stdout left empty
45
+ zen run --json --out run.json "what changed?" the whole envelope, in a file
41
46
  ```
42
47
 
43
48
  There is no `--quiet`. Narration is on stderr already, so a redirect is enough;
@@ -46,7 +51,9 @@ There is no `--quiet`. Narration is on stderr already, so a redirect is enough;
46
51
  `--json` answers with the paths as well as the answer: the session and run
47
52
  directories, every file the run left behind, and every tree the agent could see,
48
53
  with the path it had on this machine beside the path it had inside the
49
- container.
54
+ container. `--out` takes that envelope off stdout the same way it takes the
55
+ prose — with `--json` the envelope _is_ the answer, so the file holds all of it
56
+ and stdout is left empty.
50
57
 
51
58
  ```json
52
59
  {
@@ -58,7 +65,8 @@ container.
58
65
  "output": ".../output.md",
59
66
  "state": ".../state.json",
60
67
  "meta": ".../meta.json",
61
- "report": ".../report.html"
68
+ "report": ".../report.html",
69
+ "graph": ".../graph.mmd"
62
70
  },
63
71
  "mounts": [
64
72
  { "host": "...", "at": "/workspace", "readOnly": false },
@@ -76,11 +84,198 @@ container.
76
84
  `mounts` also lists the container's `$HOME` and, when keys are forwarded, the
77
85
  credential files mounted under `/run/zenera/keys`.
78
86
 
87
+ ## A request in a file
88
+
89
+ `--input <file.json>` is the whole invocation written down. It is the only way
90
+ to ask about a picture - a command line cannot carry one - and it is what makes
91
+ a run reproducible: a case is a file, so it can be checked in, diffed and re-run.
92
+
93
+ ```json
94
+ {
95
+ "project": "acme",
96
+ "input": [{ "text": "what is in this picture?" }, { "image": "./shot.png" }],
97
+ "workspace": "./ws",
98
+ "memory": "./mem"
99
+ }
100
+ ```
101
+
102
+ Only `input` is required, and it may be a plain string instead of an array. Each
103
+ part is a bare string, `{ "text": "…" }`, one of `{ "image" | "audio" | "video" |
104
+ "file": "…" }`, or the canonical `{ "type", "url", "mimeType" }` - the same shapes
105
+ the library's `Input` accepts, because the file is a JSON projection of it.
106
+
107
+ Three rules, and they are the whole of the format:
108
+
109
+ - **Paths are relative to the file**, not to the directory you ran from, so a
110
+ case travels with the images beside it. With `--input -` there is no file, so
111
+ they are relative to the cwd. A bare `project` is a registered name and is
112
+ left alone; one starting with `.` or `/` is a directory.
113
+ - **The file wins** over the same flag on the command line. A field it leaves
114
+ out falls through to the flag, then to the usual default. This is the one
115
+ place the rule above the options table is reversed - the file _is_ the
116
+ invocation, not a repository's standing intent.
117
+ - **A local media path is inlined** as a `data:` url, so the state a run resumes
118
+ from carries the picture rather than a path that meant something elsewhere.
119
+ `http(s):` and `data:` urls are passed through untouched. The bytes land in
120
+ `state.json` and are rewritten on every later turn of that session, so there
121
+ is a 20 MB ceiling and a warning well before it.
122
+
123
+ A request never opens the TUI and never asks about the workspace, exactly like a
124
+ prompt on the command line. Giving both a prompt and `--input` is an error.
125
+
126
+ ```
127
+ zen run --input case.json --json | jq -r .output
128
+ cat case.json | zen run --input - --json
129
+ zen run --input case.json --json --out result.json everything, in a file
130
+ zen run --input case.json --session 20260825-143012-a7f3 continue, don't start
131
+ ```
132
+
79
133
  `--plain` never opens the TUI. The TUI is the only thing that can ask for a
80
134
  prompt, so with `--plain` the prompt has to be there already - in the argument
81
135
  or on stdin - and `zen run --plain` with neither is an error instead of a
82
136
  window. Say it in a script that must not meet one.
83
137
 
138
+ ## Many at once - `zen run batch`
139
+
140
+ ```
141
+ zen run batch --input cases.json [--batch-dir <dir>] [--concurrency 16]
142
+ [--memory <dir>] [--memory-read-only] [--out <file>] [--json]
143
+ ```
144
+
145
+ The same file, pluralised. One project, many questions, sixteen at a time by
146
+ default:
147
+
148
+ ```json
149
+ {
150
+ "batch": [
151
+ { "id": "vat", "input": "what is the VAT threshold?" },
152
+ { "input": [{ "text": "what is this?" }, { "image": "./shot.png" }] },
153
+ { "id": "ws", "input": "read the notes", "workspace": "./cases/ws" }
154
+ ]
155
+ }
156
+ ```
157
+
158
+ An item takes `input`, an optional `id` and an optional `workspace`, and nothing
159
+ else. `project` and `memory` belong to the batch rather than to an item, so they
160
+ are flags: a file that names either is refused rather than half-honoured. An
161
+ `id` names a directory, so it has to be one - letters, digits, dot, dash and
162
+ underscore - and it defaults to the item's index. Two items may not share an
163
+ `id` or a `workspace`, because they run at the same time.
164
+
165
+ Everything refusable is refused before the first model call. A batch pays for a
166
+ mistake once per item.
167
+
168
+ ### What it leaves behind
169
+
170
+ ```
171
+ <batch-dir>/
172
+ README.md a live dashboard, rewritten every second
173
+ batch.json the index: every item, whether it worked, where it is
174
+ <id>/
175
+ workspace/ what that item could read and write
176
+ memory/ its own copy, in the copying mode only
177
+ output.json exactly what `zen run --json` prints for one run
178
+ ```
179
+
180
+ `README.md` is where the progress of a batch lives. Sixteen agents narrating at
181
+ once would be noise, so instead the file is rewritten once a second: a
182
+ fixed-height panel showing every worker - what it is running, the tail of what
183
+ it is thinking or writing, its turns and its tokens - over a `Tokens by model`
184
+ table, a table of the finished ones and a list of the ones still waiting. The
185
+ panel keeps its height while there is work left to start, so nothing jumps
186
+ between ticks. Watch it in an editor preview, or `watch -n1 cat "$DIR/README.md"`.
187
+ When the batch ends the same file is the report.
188
+
189
+ Tokens are reported **per model**: agents may each use a different one, so one
190
+ total belongs to none of them. Every model id gets a row with its calls and its
191
+ input, cached, output and thinking tokens, and the rows always add up to the
192
+ batch total. Tokens spent summarising, which no node attributes to a model, are
193
+ shown as `(compaction)`.
194
+
195
+ Each `output.json` is written the moment its item finishes, so a batch stopped
196
+ half way still has every answer it managed to get. `batch.json` is an index, not
197
+ a second copy - it points at the files:
198
+
199
+ ```json
200
+ {
201
+ "batch": {
202
+ "dir": "...",
203
+ "items": 3,
204
+ "ok": 2,
205
+ "failed": 1,
206
+ "concurrency": 16,
207
+ "memory": { "source": "...", "mode": "read-only" },
208
+ "usage": {
209
+ "inputTokens": 0,
210
+ "cachedInputTokens": 0,
211
+ "outputTokens": 0,
212
+ "reasoningTokens": 0
213
+ },
214
+ "models": [{ "model": "gemini-3-flash", "calls": 12, "usage": {} }],
215
+ "durationMs": 0
216
+ },
217
+ "batch_results": [
218
+ { "index": 0, "id": "vat", "ok": true, "input": "…", "output": ".../vat/output.json" },
219
+ {
220
+ "index": 1,
221
+ "id": "1",
222
+ "ok": false,
223
+ "input": "…",
224
+ "output": "...",
225
+ "error": { "message": "…" }
226
+ }
227
+ ]
228
+ }
229
+ ```
230
+
231
+ A failure is data. One item that cannot be answered costs one answer, not the
232
+ other ninety-nine; the exit code says how many failed, and it is set after
233
+ everything is written.
234
+
235
+ stdout is the batch directory and nothing else, so `ls "$(zen run batch --input
236
+ cases.json)"` works. `--json` prints the index there instead. Progress is in
237
+ `<batch-dir>/README.md` either way. `--out` puts the index in a second file as
238
+ well.
239
+
240
+ ### Memory, in two modes
241
+
242
+ A memory is a locked directory, so sixteen runs cannot hold one. Which leaves
243
+ two honest arrangements, and the flag picks between them:
244
+
245
+ | Mode | What happens |
246
+ | -------------------- | ------------------------------------------------------------------ |
247
+ | `--memory-read-only` | Every item recalls from the one graph. Nothing is written, no lock |
248
+ | default | Each item gets a copy under `<batch-dir>/<id>/memory` |
249
+
250
+ In the copying mode the project's own memory is never touched. Fold what the
251
+ items learned back in afterwards, having read it:
252
+
253
+ ```
254
+ zen memory merge <batch-dir>/*/memory
255
+ ```
256
+
257
+ An item that committed nothing leaves no `memory/` behind, so that line never
258
+ names a directory `merge` would refuse.
259
+
260
+ `--memory <dir>` names the source in both modes; without it the source is the
261
+ project's own memory, and a project with none runs the batch with none. A
262
+ source that does not exist yet is not an error in the copying mode: a project
263
+ declares its memory directory and the first run makes it, so every item simply
264
+ starts from an empty graph. That is how a cold project is warmed, and pointing
265
+ `--memory` at a directory that is not there is how you ask for it deliberately.
266
+ A batch refuses to start while another run holds that memory's lock.
267
+
268
+ `--memory-read-only` works on a single `zen run` too. It is how you ask a
269
+ question of what the agents know without changing what they know.
270
+
271
+ ### What means nothing in a batch
272
+
273
+ `--session`, `--new`, `--workspace`, `--plain` and `--theme` are refused by
274
+ name rather than ignored. Every item is a new session of its own, they cannot
275
+ share a workspace, and a batch never draws the TUI.
276
+
277
+ For a project or a prompt actually called "batch", say `--project batch`.
278
+
84
279
  ## Which word is the project
85
280
 
86
281
  The first positional is read as a project when it names one, and as the first
@@ -138,7 +333,7 @@ run history. Ids look like `20260825-143012-a7f3`.
138
333
  ```
139
334
  <project>/sessions/<id>/
140
335
  workspace/ what the agent sees, unless --workspace said otherwise
141
- runs/<run-id>/ input.md, output.md, state.json, report.html, meta.json
336
+ runs/<run-id>/ input.md, output.md, state.json, report.html, graph.mmd, meta.json
142
337
  .data/
143
338
  state.json the live, resumable state, rewritten after every run
144
339
  session.json when it was made, and the workspace it is rooted at
@@ -162,4 +357,6 @@ definition and is taken over.
162
357
  ## Afterwards
163
358
 
164
359
  Each run writes a `report.html` next to its state; the run prints a
165
- `file://` link to it. See [inspect.md](inspect.md).
360
+ `file://` link to it. It writes a `graph.mmd` beside it too - the same
361
+ trajectory as one Mermaid flowchart, which is what `zen inspect graph` prints.
362
+ See [inspect.md](inspect.md).
@@ -119,7 +119,16 @@ distinguishes the two cases: a broken Dockerfile is an error, a laptop with no
119
119
  Podman is a warning.
120
120
 
121
121
  `zen sandbox clean` removes every container this CLI created - the way out of a
122
- container left on a bad rootfs.
122
+ container left on a bad rootfs. `--idle` removes only what no live run is using:
123
+ stopped containers, and running ones whose run was killed before stopping them
124
+ (`zen sandbox status` marks those `orphaned`).
125
+
126
+ `could not create container … exceeded num_locks (2048)` means the engine is out
127
+ of locks: every container holds one, stopped ones included, and `persist: true`
128
+ leaves one stopped per session. `zen run` and `zen run batch` check this before
129
+ the first model call and exit `5` when the engine cannot hold them (a batch needs
130
+ one per item it runs at once). `zen sandbox status` shows a `locks` row when few
131
+ are left; `zen sandbox clean --idle` frees them.
123
132
 
124
133
  For complete container architecture, the `sandbox:*` tools, mount rules, and
125
134
  hardening details, load the `zen-sandbox` skill (`.github/skills/zen-sandbox/SKILL.md`).
@@ -0,0 +1,123 @@
1
+ ---
2
+ name: zen-code-python
3
+ description: Code an agent writes must be a reusable tool that can be remembered and re-run, never a one-off. Holds the coding rules (`references/code_python.md`) and decides where they go in the target project - a section of a dedicated code agent's system prompt, or a coding skill for generic agents that sometimes run code - how to adapt them, and what a prompt or skill for a code-writing agent must never say. Load before writing, editing or reviewing any agent prompt, skill or house rule for an agent that writes or runs code - any agent holding `sandbox:*` or `run_command` - and before shipping a script inside a skill.
4
+ ---
5
+
6
+ # Code an agent writes is a tool
7
+
8
+ A script an agent writes is worth more than the answer it printed. Written
9
+ generically - every changing value an argument, a name that says what it does -
10
+ it can be committed to memory as a `file` node and found again by the next
11
+ similar question, which runs it instead of writing it again. Written as a
12
+ one-off - this customer's id, this month, this path, as literals - it can only
13
+ ever answer the question that produced it, and memory fills up with near-copies
14
+ that never fold together.
15
+
16
+ Nothing in the runtime enforces this, and `zen check` does not report it. The
17
+ rules reach an agent only through what you write into the target project.
18
+
19
+ ## The reference
20
+
21
+ `references/code_python.md` is the rule set, in wording that has been tested on
22
+ models. It is a source to build from, not a file to copy. Start from its
23
+ sentences - copying and then editing them works; rewriting them from scratch in
24
+ your own words loses the mechanical phrasing that makes them obeyed.
25
+
26
+ **Keep in substance, and never soften:**
27
+
28
+ - every value that would change for the next similar question is a named
29
+ argument, never a literal
30
+ - the file is named for what it does, and describes itself through `--help`
31
+ - the result goes to stdout, a produced file to an `--output` path
32
+ - the script is written to a file and the file is run - no `python -c`, no
33
+ heredoc
34
+ - before using the output, the script is checked against a different question
35
+ of the same kind
36
+ - with memory: search before writing, commit the working script after
37
+
38
+ **Adapt to the project:**
39
+
40
+ - the interpreter and language, if the sandbox image runs something other than
41
+ Python
42
+ - where scripts are written - `/workspace/scripts/` unless the project already
43
+ has a place
44
+ - libraries the image carries and the agent should prefer, and the output
45
+ format the project expects (JSON, CSV, a table)
46
+ - an example invocation from this project's own domain, values passed as
47
+ arguments
48
+ - the memory steps: keep them when the agent holds `memory_search` /
49
+ `memory_commit`, drop them when it never will. In a skill several agents may
50
+ load, keep them gated on the tool, as the reference does
51
+
52
+ ## Decide where the rules go
53
+
54
+ List the agents that hold `sandbox:*` or `run_command`, and read each one's
55
+ prompt for what its job is. Then:
56
+
57
+ | The project has | Write |
58
+ | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
59
+ | A **dedicated code agent** - a runner, an executor, an analyst whose answers are scripts | A `## Writing code` section in that agent's `agents/prompts/<name>.md` |
60
+ | **No dedicated code agent** - generic agents that sometimes run code | A coding skill, `agents/skills/code_python/SKILL.md` |
61
+ | Both | The prompt section, and the generic agents hand the code work to that agent rather than writing it - see below |
62
+ | No agent that writes or runs code | Nothing |
63
+
64
+ **A dedicated code agent gets the rules in its system prompt.** It writes code
65
+ on nearly every turn, so the rules are part of its job description: a skill
66
+ would cost a load on every case, and preloading one is the same text in the
67
+ prefix with an extra file to keep in step. Put the section after the agent's
68
+ identity and procedure, before its prohibitions.
69
+
70
+ **Generic agents get a skill.** Most of their turns write no code, so the rules
71
+ should cost nothing until one does. The `description` is the routing key - name
72
+ the condition, not the topic:
73
+
74
+ ```yaml
75
+ description: Before writing, changing or running any Python script - how to write it as a reusable tool rather than a one-off.
76
+ ```
77
+
78
+ Leave it in the agent's index; do not preload it. If `allow` is used, add
79
+ `code_python` to it for every agent holding `sandbox:*`.
80
+
81
+ **With both, one copy.** Generic agents that can reach a code agent should
82
+ `fork:` to it for an answer, or hand off to it, rather than writing code
83
+ themselves - then the rules have one home and the scripts one author. Grant
84
+ them no `sandbox:*` for code work. If they must keep a shell of their own, they
85
+ need the skill too, and its rules are the same sentences as the prompt section:
86
+ a second wording is how one copy ends up softer than the other, and the softer
87
+ one is the one obeyed.
88
+
89
+ Run `zen check` after any change to `agents.yaml` or a skill.
90
+
91
+ ## Writing anything else around it
92
+
93
+ - **One home per agent.** Once an agent has the rules - in its prompt or in its
94
+ skill - no house rule, other skill or prompt line restates them. A prompt that
95
+ needs to point at the skill names it: _"Follow `code_python` for every
96
+ script."_
97
+ - **Never add an exemption.** _"Hard-coding is fine for a quick one-off check"_
98
+ is a permission: every probe is a one-off by construction, so the exception
99
+ admits exactly what the rule forbids. The same goes for `python -c`.
100
+ - **Never show a one-off as an example.** A prompt or skill that shows
101
+ `python -c "..."`, or a script with a literal id, date or path in its body,
102
+ teaches the shape the rule forbids. Every example invocation passes its values
103
+ as named arguments:
104
+ `python /workspace/scripts/list_overdue_invoices.py --customer-id 42`.
105
+ - **Scripts shipped inside a skill** (`agents/skills/<name>/scripts/`, §3.4.1 of
106
+ the copilot instructions) follow the same rules: named arguments, a `--help`,
107
+ the result on stdout, an `--output` path under `/workspace` for any file,
108
+ since `/skills` is read-only.
109
+ - **Memory is what makes the reuse happen.** A code-writing agent without
110
+ memory still writes reusable scripts, but cannot find them in a later session.
111
+ Say so if the project expects reuse and that agent has no `memory:`.
112
+
113
+ ## Review
114
+
115
+ - [ ] Every agent that writes or runs code has the rules - a prompt section for
116
+ a dedicated code agent, the `code_python` skill reachable for the others
117
+ - [ ] Every rule under _Keep in substance_ is there, none softened, and the
118
+ memory steps match whether the agent holds the memory tools
119
+ - [ ] Generic agents beside a dedicated code agent hand code work to it, or
120
+ carry the same wording - not a second paraphrase
121
+ - [ ] No other prompt, skill or house rule restates the rules, adds an
122
+ exemption, or shows `python -c` or a script with literal values
123
+ - [ ] Every script shipped in a skill takes its values as named arguments
@@ -0,0 +1,48 @@
1
+ # Writing Python scripts
2
+
3
+ Every script you write is a tool for the next question like this one, not an
4
+ answer to this one. Write it once, generically, so it can be remembered and run
5
+ again unchanged.
6
+
7
+ ## Before writing
8
+
9
+ If you hold `memory_search`, search for a script that already answers this kind
10
+ of question, and load the best match with `memory_load`. If it fits, run it with
11
+ this question's arguments and write nothing new. If it needs a change, change
12
+ it the way the rest of this skill says - usually by adding an argument - and
13
+ commit the new version as superseding the old one.
14
+
15
+ ## Writing
16
+
17
+ 1. **Solve the kind of question, not this one.** Anything that would differ the
18
+ next time - inputs, ids, names, paths, urls, dates and date ranges, filters,
19
+ thresholds, limits, the output path - is a named `argparse` argument such as
20
+ `--resource-id`, never a literal in the code. A default is allowed only for a
21
+ value that is the same for every question.
22
+ 2. **Name the file for what it does**, verb first: `list_overdue_invoices.py`,
23
+ `compare_quarterly_revenue.py`. Never `script.py`, `test.py`, `tmp.py` or a
24
+ name that only makes sense in this conversation.
25
+ 3. **Describe it.** Line one is a docstring saying what question it answers. The
26
+ same sentence is the `argparse` description, and every argument has a
27
+ `help=`, so `--help` is all a later reader needs.
28
+ 4. **Print the result to stdout** and anything else to stderr. Exit non-zero
29
+ with a message saying what failed when it cannot answer. A file it produces
30
+ goes to the path given by `--output`.
31
+ 5. **Take credentials from environment variables by name.** Never write a key,
32
+ token or password into the code or an argument default.
33
+ 6. **Use only what the sandbox image already has.** Do not `pip install` from
34
+ the script or before running it.
35
+ 7. **Write it to a file and run the file**:
36
+ `python /workspace/scripts/<name>.py --resource-id 42`. Never run Python
37
+ with `python -c` or a heredoc.
38
+
39
+ ## Before using its output
40
+
41
+ - Run it, and fix the script - not the output - when it fails or prints
42
+ something wrong.
43
+ - Read your own code against a different question of the same kind. If
44
+ answering it would mean editing the script rather than passing other
45
+ arguments, a value is still hard-coded: make it an argument and run again.
46
+ - If you hold `memory_commit`, commit the working script as a `file` node. Its
47
+ text says what kind of question it answers and names its arguments, so that a
48
+ later search for a similar question finds it.