@joenandez/academy 0.5.17-rc.2 → 0.5.18

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 (70) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +150 -0
  3. package/README.md +7 -4
  4. package/package.json +2 -2
  5. package/scripts/cli/agent-gate.mjs +20 -10
  6. package/scripts/cli/agent-row.mjs +24 -0
  7. package/scripts/cli/agent-settings.mjs +104 -0
  8. package/scripts/cli/args-kinds.mjs +59 -8
  9. package/scripts/cli/args-owner.mjs +9 -5
  10. package/scripts/cli/args.mjs +44 -11
  11. package/scripts/cli/copilot/child-env.mjs +2 -0
  12. package/scripts/cli/core.mjs +8 -7
  13. package/scripts/cli/create-options.mjs +43 -0
  14. package/scripts/cli/create.mjs +21 -41
  15. package/scripts/cli/curator-feed-observations.mjs +38 -4
  16. package/scripts/cli/curator-feed-sources.mjs +9 -10
  17. package/scripts/cli/doctor.mjs +3 -0
  18. package/scripts/cli/heartbeat.mjs +9 -39
  19. package/scripts/cli/inspect.mjs +6 -1
  20. package/scripts/cli/leads.mjs +94 -0
  21. package/scripts/cli/lifecycle.mjs +3 -0
  22. package/scripts/cli/main.mjs +6 -1
  23. package/scripts/cli/model.mjs +45 -0
  24. package/scripts/cli/nightly.mjs +46 -16
  25. package/scripts/cli/owner-jobs.mjs +23 -4
  26. package/scripts/cli/owner-sessions.mjs +5 -5
  27. package/scripts/cli/rename.mjs +55 -17
  28. package/scripts/cli/run.mjs +3 -2
  29. package/scripts/cli/scaffold.mjs +12 -6
  30. package/scripts/cli/skill-resource-paths.mjs +140 -0
  31. package/scripts/cli/skill-resources.mjs +158 -0
  32. package/scripts/cli/skills.mjs +13 -53
  33. package/scripts/cli/start.mjs +21 -7
  34. package/scripts/cli/yaml.mjs +10 -0
  35. package/skills/hire/references/owner.md +6 -4
  36. package/templates/agents/claude-code/worker.md +1 -0
  37. package/templates/agents/codex/worker.toml +1 -0
  38. package/templates/operating-model/base.md +31 -11
  39. package/templates/operating-model/owner-loop-off.md +9 -0
  40. package/templates/operating-model/owner.md +3 -2
  41. package/templates/packs/owner/craft.md +2 -2
  42. package/templates/packs/owner/mandate.md +3 -0
  43. package/templates/packs/owner/owner/SKILL.md +31 -26
  44. package/templates/packs/owner/owner-feedback/SKILL.md +4 -3
  45. package/templates/packs/owner/owner-heartbeat/SKILL.md +5 -20
  46. package/templates/packs/owner/owner-nightly/SKILL.md +10 -17
  47. package/templates/packs/owner/owner-review/SKILL.md +5 -4
  48. package/templates/packs/owner/owner-work/SKILL.md +28 -19
  49. package/templates/packs/principal/academy-performance-review/SKILL.md +91 -0
  50. package/templates/packs/principal/academy-performance-review/references/operating-standard.md +59 -0
  51. package/templates/packs/principal/principal-brief/SKILL.md +23 -7
  52. package/templates/packs/principal/principal-nightly/SKILL.md +6 -7
  53. package/templates/skills/delivery/SKILL.md +18 -0
  54. package/templates/skills/delivery/references/build.md +15 -0
  55. package/templates/skills/delivery/references/experience.md +12 -0
  56. package/templates/skills/delivery/references/orchestration.md +15 -0
  57. package/templates/skills/delivery/references/plan.md +15 -0
  58. package/templates/skills/delivery/references/run.md +26 -0
  59. package/templates/skills/delivery/references/scope.md +15 -0
  60. package/templates/skills/delivery/references/ship.md +12 -0
  61. package/templates/skills/delivery/references/validate.md +16 -0
  62. package/templates/skills/explore-options/SKILL.md +66 -0
  63. package/templates/skills/knowledge-curation/SKILL.md +2 -3
  64. package/templates/skills/nightly-consolidation/SKILL.md +9 -4
  65. package/templates/skills/skill-method/SKILL.md +2 -2
  66. package/templates/skills/triage/SKILL.md +119 -0
  67. package/templates/skills/triage/references/curator.md +14 -0
  68. package/templates/skills/triage/references/rank.md +39 -0
  69. package/templates/skills/user-nightly/SKILL.md +18 -10
  70. package/templates/skills/work-graph/SKILL.md +6 -3
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "academy",
3
3
  "description": "Academy v3 — portable AI agents.",
4
- "version": "0.5.17-rc.2",
4
+ "version": "0.5.18",
5
5
  "hooks": "./hooks/hooks.json"
6
6
  }
package/CHANGELOG.md CHANGED
@@ -3,6 +3,156 @@
3
3
  This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4
4
  The heading at the top of this file is the version in `package.json`.
5
5
 
6
+ ## 0.5.18 — 2026-11-11
7
+
8
+ ### Fixed
9
+
10
+ - **`academy create --runtime claude-code|codex|copilot`.** `create` stores
11
+ the agent's launch runtime, the same value `run --agent` persists, before
12
+ its jobs register. Before, `create` refused the flag, so a client that hired
13
+ a Codex agent (Subspace's Hire a Principal) failed. An unknown value is
14
+ refused with `invalid_spec` and writes nothing.
15
+
16
+ ## 0.5.17 — 2026-11-11
17
+
18
+ This release collects the 0.5.17 release candidates (rc.1 to rc.6, below) and
19
+ the work merged after rc.6. npm `latest` was 0.5.13 before this release; the
20
+ 0.5.14 to 0.5.16 notes below ship with it.
21
+
22
+ ### Added
23
+
24
+ - **`tldr-agents` and `backstory` commands.** A global install puts
25
+ `academy`, `tldr-agents`, and `backstory` on your PATH. `tldr-agents` runs
26
+ the same tldr that Academy uses.
27
+ - **Delivery for every agent kind.** One skill links Scope, Plan, Build,
28
+ Validate, and Ship, with worker delegation, TDD, risk-based review, and
29
+ resumable execution checkpoints. Skills can bundle linked Markdown
30
+ references.
31
+ - **Work triage and a ranked priority list.** A `triage` skill gives each Beads
32
+ item one structured state. Owners and Principals keep a ranked priority
33
+ list, triage checks it daily, and Principals sync it to the user's Workstream
34
+ order. Agents search Beads before they file and report status from Beads
35
+ only.
36
+ - **Academy Operating Standard.** New `explore-options` and
37
+ `academy-performance-review` skills.
38
+ - **A per-agent default model, Leads, and `--display-name`.** `academy create`
39
+ and `academy rename` take `--display-name`; an agent can have a default
40
+ model and Leads.
41
+ - **`academy list --agent-row <name>`** reports one agent's row, including a
42
+ draft Owner.
43
+ - **Owner cadence.** Owners size work to the mandate's cadence, recover due
44
+ check-ins without a nightly run, and keep reviews within cadence while work
45
+ waits. The first Owner welcome does not depend on cadence.
46
+ - **The Principal morning brief goes to every channel** the user set up and
47
+ covers the Principal's own work and queue. The user-nightly learns the
48
+ user's preferences.
49
+
50
+ ### Changed
51
+
52
+ - **One install command.** Install with `npm install -g @joenandez/academy`,
53
+ then run `tldr-agents setup`. tldr comes from the npm registry, pinned to
54
+ `@joenandez/tldr` 0.3.0-rc.3; Academy no longer bundles it. If you installed
55
+ `@joenandez/tldr` globally, uninstall it first.
56
+ - **Stopping an agent turns off its work loop.** Nightly consolidation stays
57
+ scheduled. An Owner heartbeat only restarts stopped work.
58
+ - **Nightly consolidation waits for 10 new observations.** Smaller inputs carry
59
+ over to a later run.
60
+ - **Nightly and Owner daily jobs have a late-start window**, as does the
61
+ Principal morning brief.
62
+ - **Subagent guidance.** Agents use workers for bounded work and handle small
63
+ checks themselves.
64
+ - Each Workstream names its production version and release cycle (`Prod:`).
65
+
66
+ ### Fixed
67
+
68
+ - **`npm install -g` no longer fails on macOS.** The registry dependency
69
+ installs with default npm settings.
70
+ - **Copilot setup on macOS without the Claude Code tldr plugin.** Academy
71
+ skips the runtime-only copy and uses the npm package. The Copilot pre-launch
72
+ error names `tldr-agents setup`.
73
+ - A one-run runtime switch (`--runtime-once`) launches without the stored
74
+ model.
75
+ - Owner stopped-claim recovery, the Copilot cold-start placeholder id, a
76
+ late-swept observation file, and stopped-agent loop checks (identified by
77
+ scheduler job ID).
78
+ - The triage backfill acts only where the live state matches its backup.
79
+
80
+ ### Removed
81
+
82
+ - **The bundled tldr** (`vendor/tldr`).
83
+
84
+ ### Install / upgrade
85
+
86
+ - Run `npm install -g @joenandez/academy`, then run `tldr-agents setup` to
87
+ replace any old Copilot hooks record.
88
+
89
+ ## 0.5.17-rc.6 — 2026-10-28
90
+
91
+ ### Fixed
92
+
93
+ - Keep delivery's internal resource helpers private so the package passes the
94
+ required dead-code gate. This replaces the local rc.5 candidate; neither RC
95
+ was published to npm or the public repository.
96
+
97
+ ## 0.5.17-rc.5 — 2026-10-28
98
+
99
+ ### Added
100
+
101
+ - **Delivery for every agent kind.** One compact skill links Scope, Plan, Build,
102
+ Validate, and Ship. Agents load only the stage they need. It includes worker
103
+ delegation, TDD, product validation, risk-based adversarial review, durable
104
+ feature records, and resumable execution checkpoints.
105
+ - **Bundled skill references.** Agent creation and launch refresh install linked
106
+ Markdown references on both runtime skill surfaces. Refresh preserves unknown
107
+ files and reports ownership conflicts before writing.
108
+
109
+ ### Changed
110
+
111
+ - **Stopping an agent turns off its work loop.** Nightly consolidation remains
112
+ scheduled. A stopped Owner receives the loop-off operating model and can still
113
+ respond to on-demand work.
114
+ - **Nightly consolidation waits for 20 new observations.** Smaller inputs carry
115
+ over to a later run.
116
+ - **Owner heartbeats only restart stopped work.** A heartbeat no longer starts a
117
+ session for idle ready work; the Owner paces itself with booked sessions and
118
+ the daily open. Reverts the idle start from #4.
119
+
120
+ ### Fixed
121
+
122
+ - A late-swept observation file still counts as waiting nightly input.
123
+ - Stopped-agent checks identify loop runs by scheduler job ID, so they do not
124
+ block unrelated on-demand sessions.
125
+
126
+ ## 0.5.17-rc.4 — 2026-10-28
127
+
128
+ ### Added
129
+
130
+ - **Work triage.** A new `triage` skill for every agent kind. Each Beads item
131
+ has one structured state: untriaged, triaged, next, now, blocked, waiting on
132
+ the user's decision (a human gate), PR open, merged but not released,
133
+ parked, or done with proof. Principals triage before the morning brief, and
134
+ Owners at the start of the daily open. Each nightly makes statuses true, and
135
+ the curator cleans up duplicates and items with no owner or Workstream. On
136
+ its first run, each agent drafts its own ranking criteria for the user to
137
+ confirm and converts its open items.
138
+ - **Capture, status, and surfacing rules** in every session. Agents search
139
+ Beads before they file and add to a match. They report status from Beads
140
+ only, and show related open items when the user discusses an area.
141
+
142
+ ### Changed
143
+
144
+ - Each Workstream names its production version and release cycle (`Prod:`).
145
+ - New Owner tasks take their Workstream's `owner:` label.
146
+
147
+ ## 0.5.17-rc.3 — 2026-10-27
148
+
149
+ ### Changed
150
+
151
+ - **Subagent guidance.** Agents use workers for bounded work and handle small
152
+ checks themselves. They check the model and capacity pool, pass context and
153
+ evidence requirements, run independent tasks in parallel, and verify the
154
+ result. Owners delegate substantial execution and handle small checks.
155
+
6
156
  ## 0.5.17-rc.2 — 2026-10-27
7
157
 
8
158
  ### Fixed
package/README.md CHANGED
@@ -70,7 +70,7 @@ Six universal skills ship with every agent: `check-in`, `work-graph`, `nightly-c
70
70
 
71
71
  ## Commands
72
72
 
73
- Sixteen commands are contract. `doctor` publishes the list, and a client should read it from there rather than hard-coding it.
73
+ Eighteen commands are contract. `doctor` publishes the list, and a client should read it from there rather than hard-coding it.
74
74
 
75
75
  ```bash
76
76
  academy doctor [--json] # discovery, health, and the version source
@@ -80,16 +80,19 @@ academy tokens <name> [--json] # compiled prompt size by surface
80
80
  academy budget <name> [--json] # surfaces over their cap
81
81
  academy sessions [--agent <name>] [--json]
82
82
  academy events --since <seq> [--logid <id>] [--json]
83
- academy create [<name>] [--json] # no name: the next default for the kind
83
+ academy create [<name>] [--kind <kind>] [--model <id>] [--display-name <text>] [--runtime claude-code|codex|copilot] [--no-start] [--json]
84
84
  academy hire # interactive
85
85
  academy hire --spec <file> [--json] # headless
86
- academy rename <old> <new> [--json] # changes the name only; no job changes
86
+ academy rename <old> [<new>] [--display-name <text>] [--json] # no folder or job changes
87
87
  academy archive <name> [--json]
88
88
  academy unarchive <name> [--json]
89
89
  academy delete <name> [--json]
90
90
  academy migrate [--dry-run] [--json]
91
91
  academy start <name> [--mandate <file>] [--json] # register jobs; an Owner needs a signed mandate
92
- academy stop <name> [--json] # delete the jobs; the files stay
92
+ academy stop <name> [--json] # loop off; keeps the nightly and files
93
+ academy set-model <name> <model> [--json] # default model; run passes it on
94
+ academy lead set <agent> --project <path> [--json] # one Lead per project
95
+ academy lead clear --project <path> [--json]
93
96
  ```
94
97
 
95
98
  `run`, `nightly`, `notes`, `clean`, and `root` also work and are deliberately not contract: `run` spawns with inherited stdio and can never emit an envelope, `nightly` is called by the scheduler, and the rest are conveniences for a person.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joenandez/academy",
3
- "version": "0.5.17-rc.2",
3
+ "version": "0.5.18",
4
4
  "description": "Academy v3 — portable AI agents with 8 boot surfaces and a skills primitive.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -45,7 +45,7 @@
45
45
  "knip": "5.70.2"
46
46
  },
47
47
  "dependencies": {
48
- "@joenandez/tldr": "0.3.0-rc.2",
48
+ "@joenandez/tldr": "0.3.0-rc.3",
49
49
  "acpx": "0.21.0"
50
50
  }
51
51
  }
@@ -3,11 +3,13 @@ import { join } from 'node:path';
3
3
  import { currentAgentName } from './agent-id.mjs';
4
4
  import { CLI_NAME, kindOf } from './core.mjs';
5
5
  import { printHelmSkip } from './helm-skip.mjs';
6
+ import { ownerJobIds, ownerSessionJobIds, principalJobIds } from './owner-jobs.mjs';
6
7
  import { schedulerJobId } from './tldr.mjs';
7
8
  import { readAgentYaml } from './yaml.mjs';
8
9
 
9
- // Decision D1: nothing runs until the user signs the mandate, and a stopped
10
- // agent runs nothing. Job registration and every run entry ask these two.
10
+ // Decision D1: nothing runs until the user signs the mandate. A stopped agent
11
+ // runs no loop job (daily, heartbeat, next, eod, brief); a person, a message
12
+ // wake and the nightly still run. Job registration and every run entry ask these.
11
13
 
12
14
  // The hire placeholder the Owner mandate template carries until a person
13
15
  // writes the mandate. Matched as a substring, so the template may change around it.
@@ -32,14 +34,20 @@ export function isDraftOwner(dir) {
32
34
  }
33
35
 
34
36
  // Of a kind's job ids, nightly first, the ones registered while the agent is
35
- // in this state: none when stopped, only the nightly for a draft Owner.
37
+ // in this state: only the nightly when stopped or for a draft Owner.
36
38
  export function liveJobIds(dir, ids) {
37
- if (isStopped(dir)) return [];
38
- return isDraftOwner(dir) ? ids.slice(0, 1) : ids;
39
+ return isStopped(dir) || isDraftOwner(dir) ? ids.slice(0, 1) : ids;
39
40
  }
40
41
 
41
- function notStartedReason(dir) {
42
- if (isStopped(dir)) return 'agent_stopped';
42
+ // A loop run is a run whose scheduler job id is a loop job's id. Env flags such
43
+ // as ACADEMY_OWNER_RUN do not count: children inherit them.
44
+ const isLoopRun = (id) =>
45
+ [...ownerJobIds(id), ...ownerSessionJobIds(id), ...principalJobIds(id)].includes(
46
+ schedulerJobId(),
47
+ );
48
+
49
+ function notStartedReason(dir, id) {
50
+ if (isStopped(dir) && isLoopRun(id)) return 'agent_stopped';
43
51
  if (isDraftOwner(dir)) return 'owner_draft';
44
52
  return null;
45
53
  }
@@ -48,7 +56,7 @@ function notStartedReason(dir) {
48
56
  // failed delete included, ends as a skip the scheduler records; a person is
49
57
  // told to start the agent.
50
58
  export function refuseNotStarted(dir, id) {
51
- const reason = notStartedReason(dir);
59
+ const reason = notStartedReason(dir, id);
52
60
  if (!reason) return;
53
61
  const jobId = schedulerJobId();
54
62
  console.error(
@@ -63,8 +71,10 @@ export function refuseNotStarted(dir, id) {
63
71
  printHelmSkip(reason, id);
64
72
  process.exit(0);
65
73
  }
74
+ // Only owner_draft reaches a person: agent_stopped needs a loop job id.
66
75
  const name = currentAgentName(dir);
67
- const state = reason === 'agent_stopped' ? 'is stopped' : 'has no signed mandate';
68
- console.error(`Agent "${name}" ${state}. Run \`${CLI_NAME} start ${name}\` to start it.`);
76
+ console.error(
77
+ `Agent "${name}" has no signed mandate. Run \`${CLI_NAME} start ${name}\` to start it.`,
78
+ );
69
79
  process.exit(1);
70
80
  }
@@ -0,0 +1,24 @@
1
+ import { isDraftOwner, isStopped } from './agent-gate.mjs';
2
+ import { agentIdOf } from './agent-id.mjs';
3
+ import { agentNames, agentRecord } from './inspect.mjs';
4
+ import { toRuntimeToken } from './runtime.mjs';
5
+
6
+ // One row for tldr's message wake: where to start the agent and on which runtime.
7
+ // Any kind answers. An archived agent is not in the roster, so it reads as unknown.
8
+ // draft: an Owner with no signed mandate, which academy run refuses to start.
9
+ export function printAgentRow(name) {
10
+ const id = agentIdOf(name);
11
+ if (!agentNames().includes(id)) {
12
+ console.error(`Error: "${name}" is not an agent in this Academy.`);
13
+ process.exit(1);
14
+ }
15
+ const { name: agent, dir, runtimeProvider } = agentRecord(id, { strictRuntime: true });
16
+ const row = {
17
+ name: agent,
18
+ cwd: dir,
19
+ runtime: toRuntimeToken(runtimeProvider),
20
+ stopped: isStopped(dir),
21
+ draft: isDraftOwner(dir),
22
+ };
23
+ console.log(JSON.stringify(row));
24
+ }
@@ -0,0 +1,104 @@
1
+ import { statSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import {
4
+ LockTimeoutError,
5
+ agentDir,
6
+ agentLifecycleLockPath,
7
+ contractOk,
8
+ exitJsonError,
9
+ withFileLock,
10
+ } from './core.mjs';
11
+ import { agentRecord } from './inspect.mjs';
12
+ import { writeLead } from './leads.mjs';
13
+ import { preflightOwnedAgent, reportLifecycleFailure } from './lifecycle.mjs';
14
+ import { agentModel, isModelId } from './model.mjs';
15
+ import { rewriteAgentYamlScalar } from './yaml.mjs';
16
+
17
+ // `set-model` and `lead`: the two settings Academy keeps for a client that
18
+ // cannot keep them itself. Neither is in the frozen v1 command list; both answer
19
+ // in the envelope with the published codes.
20
+
21
+ function refuse(code, message, fields, json) {
22
+ if (json) exitJsonError(code, message, fields);
23
+ console.error(`Error: ${message}`);
24
+ process.exit(1);
25
+ }
26
+
27
+ // `set-model <name> <model> [--json]`: rewrites the agent.yaml `model:` scalar.
28
+ export function setAgentModel({ name, model, json, invalidOption }) {
29
+ if (invalidOption !== undefined) {
30
+ const usage = 'Use set-model <name> <model> [--json].';
31
+ refuse('invalid_spec', `Unknown set-model option: ${invalidOption}. ${usage}`, {}, json);
32
+ }
33
+ if (!isModelId(model)) {
34
+ refuse('invalid_spec', `Model "${model ?? ''}" is not a model id Academy can store.`, {}, json);
35
+ }
36
+ const { id } = preflightOwnedAgent(name, json);
37
+ const dir = agentDir(id);
38
+ let previousModel;
39
+ try {
40
+ withFileLock(agentLifecycleLockPath(dir), () => {
41
+ previousModel = agentModel(dir);
42
+ rewriteAgentYamlScalar(dir, 'model', model);
43
+ });
44
+ } catch (error) {
45
+ reportLifecycleFailure(error, 'set the model of', name, json);
46
+ }
47
+ if (json) contractOk('set-model', { ...agentRecord(id), previousModel });
48
+ else console.log(`Default model for "${name}" is now ${model}.`);
49
+ }
50
+
51
+ const LEAD_USAGE =
52
+ 'Use lead set <agent> --project <path> [--json] or lead clear --project <path> [--json].';
53
+
54
+ // Checked in order; the first rule that matches names the fault.
55
+ const LEAD_ARG_RULES = [
56
+ [(a) => a.invalidOption !== undefined, (a) => `Unknown lead option: ${a.invalidOption}.`],
57
+ [
58
+ (a) => !['set', 'clear'].includes(a.action),
59
+ (a) => `Unknown lead action: ${a.action ?? '(none)'}.`,
60
+ ],
61
+ [(a) => !a.project, () => 'lead needs --project <path>.'],
62
+ [(a) => a.action === 'set' && !a.name, () => 'lead set needs an agent.'],
63
+ [(a) => a.action === 'clear' && a.name, () => 'lead clear takes no agent.'],
64
+ ];
65
+
66
+ function leadArgsFault(parsed) {
67
+ const rule = LEAD_ARG_RULES.find(([matches]) => matches(parsed));
68
+ return rule ? rule[1](parsed) : null;
69
+ }
70
+
71
+ function isDirectory(path) {
72
+ try {
73
+ return statSync(path).isDirectory();
74
+ } catch {
75
+ return false;
76
+ }
77
+ }
78
+
79
+ // `lead set <agent> --project <path>` / `lead clear --project <path>`. One Lead per
80
+ // project: a set replaces the project's previous Lead. The project is its
81
+ // normalized absolute path (relative to the cwd when relative).
82
+ export function leadCommand(parsed) {
83
+ const { action, name, json } = parsed;
84
+ const fault = leadArgsFault(parsed);
85
+ if (fault) refuse('invalid_spec', `${fault} ${LEAD_USAGE}`, {}, json);
86
+ const project = resolve(parsed.project);
87
+ let id = null;
88
+ if (action === 'set') {
89
+ ({ id } = preflightOwnedAgent(name, json));
90
+ if (!isDirectory(project)) {
91
+ refuse('invalid_spec', `Project folder not found: ${project}`, { project }, json);
92
+ }
93
+ }
94
+ let previousLead;
95
+ try {
96
+ previousLead = writeLead(project, id);
97
+ } catch (error) {
98
+ if (!(error instanceof LockTimeoutError)) throw error;
99
+ refuse(error.code, error.message, error.fields, json);
100
+ }
101
+ if (json) contractOk('lead', { action, project, lead: id, previousLead });
102
+ else if (id) console.log(`"${name}" now leads ${project}.`);
103
+ else console.log(`${project} has no Lead.`);
104
+ }
@@ -1,22 +1,42 @@
1
- // `create <name> [--kind <kind>] [--no-start] [--json]`. Like `rename`, an option Academy
2
- // does not know stays on the command so the handler answers it inside the
3
- // envelope the caller asked for. The kind stays a raw string: only `create` can
4
- // answer an unsupported one in the envelope.
1
+ // `create <name> [--kind <kind>] [--model <id>] [--display-name <text>]
2
+ // [--runtime <runtime>] [--no-start] [--json]`. Like
3
+ // `rename`, an option Academy does not know stays on the command so the handler
4
+ // answers it inside the envelope the caller asked for. The kind and model stay
5
+ // raw strings: only `create` can answer an unsupported one in the envelope.
6
+ const CREATE_VALUE_FLAGS = {
7
+ '--kind': 'kind',
8
+ '--model': 'model',
9
+ '--display-name': 'displayName',
10
+ '--runtime': 'runtime',
11
+ };
12
+
5
13
  export function parseCreateArgs(command, rest) {
6
14
  const json = rest.includes('--json');
15
+ const values = {};
7
16
  let name;
8
- let kind;
9
17
  let noStart = false;
10
18
  for (let i = 0; i < rest.length; i++) {
11
19
  const arg = rest[i];
20
+ const value = createValueFlag(rest, i);
12
21
  if (arg === '--json') continue;
13
22
  else if (arg === '--no-start') noStart = true;
14
- else if (arg === '--kind') kind = rest[++i] ?? '';
15
- else if (arg.startsWith('--kind=')) kind = arg.slice('--kind='.length);
23
+ else if (value) [values[value.key], i] = [value.value, value.next];
16
24
  else if (arg.startsWith('--')) return { command, json, name, invalidOption: arg };
17
25
  else name ??= arg;
18
26
  }
19
- return { command, json, name, kind, noStart };
27
+ const { kind, model, displayName, runtime } = values;
28
+ return { command, json, name, kind, model, displayName, runtime, noStart };
29
+ }
30
+
31
+ // `--flag value` or `--flag=value` for a create option that takes a value.
32
+ function createValueFlag(rest, i) {
33
+ const arg = rest[i];
34
+ const eq = arg.indexOf('=');
35
+ const flag = arg.startsWith('--') && eq > 0 ? arg.slice(0, eq) : arg;
36
+ const key = CREATE_VALUE_FLAGS[flag];
37
+ if (!key) return null;
38
+ if (flag !== arg) return { key, value: arg.slice(eq + 1), next: i };
39
+ return { key, value: rest[i + 1] ?? '', next: i + 1 };
20
40
  }
21
41
 
22
42
  // `skills check <agent> [--json]`. Like `create`, an option Academy does not
@@ -31,3 +51,34 @@ export function parseSkillsArgs(command, rest) {
31
51
  }
32
52
  return { command, json, action: positional[0], name: positional[1] };
33
53
  }
54
+
55
+ // `set-model <name> <model> [--json]`. The model stays a raw string, so the
56
+ // handler answers a missing or unusable one in the envelope.
57
+ export function parseSetModelArgs(command, rest) {
58
+ const json = rest.includes('--json');
59
+ const positional = [];
60
+ for (const arg of rest) {
61
+ if (arg === '--json') continue;
62
+ else if (arg.startsWith('--')) return { command, json, invalidOption: arg };
63
+ else positional.push(arg);
64
+ }
65
+ return { command, json, name: positional[0], model: positional[1] };
66
+ }
67
+
68
+ // `lead set <agent> --project <path> [--json]` and `lead clear --project <path> [--json]`.
69
+ export function parseLeadArgs(command, rest) {
70
+ const json = rest.includes('--json');
71
+ const positional = [];
72
+ let project;
73
+ for (let i = 0; i < rest.length; i++) {
74
+ const arg = rest[i];
75
+ if (arg === '--json') continue;
76
+ else if (arg === '--project') project = rest[++i] ?? '';
77
+ else if (arg.startsWith('--project=')) project = arg.slice('--project='.length);
78
+ else if (arg.startsWith('--')) return { command, json, invalidOption: arg };
79
+ else positional.push(arg);
80
+ }
81
+ const [action, name, extra] = positional;
82
+ if (extra !== undefined) return { command, json, invalidOption: extra };
83
+ return { command, json, action, name, project };
84
+ }
@@ -1,15 +1,19 @@
1
- // `rename <old> <new> [--json]`. Like `events`, `migrate` and `sessions`, an
2
- // option Academy does not know stays on the command so the handler answers it
3
- // inside the envelope the caller asked for.
1
+ // `rename <old> [<new>] [--display-name <text>] [--json]`. Like `events`,
2
+ // `migrate` and `sessions`, an option Academy does not know stays on the command
3
+ // so the handler answers it inside the envelope the caller asked for.
4
4
  export function parseRenameArgs(command, rest) {
5
5
  const json = rest.includes('--json');
6
6
  const positional = [];
7
- for (const arg of rest) {
7
+ let displayName;
8
+ for (let i = 0; i < rest.length; i++) {
9
+ const arg = rest[i];
8
10
  if (arg === '--json') continue;
11
+ else if (arg === '--display-name') displayName = rest[++i] ?? '';
12
+ else if (arg.startsWith('--display-name=')) displayName = arg.slice('--display-name='.length);
9
13
  else if (arg.startsWith('--')) return { command, json, invalidOption: arg };
10
14
  else positional.push(arg);
11
15
  }
12
- return { command, json, name: positional[0], newName: positional[1] };
16
+ return { command, json, name: positional[0], newName: positional[1], displayName };
13
17
  }
14
18
 
15
19
  const START_OPTIONS = {
@@ -1,4 +1,9 @@
1
- import { parseCreateArgs, parseSkillsArgs } from './args-kinds.mjs';
1
+ import {
2
+ parseCreateArgs,
3
+ parseLeadArgs,
4
+ parseSetModelArgs,
5
+ parseSkillsArgs,
6
+ } from './args-kinds.mjs';
2
7
  import { parseNotesArgs } from './args-notes.mjs';
3
8
  import { parseRenameArgs, parseStartArgs } from './args-owner.mjs';
4
9
  import { parseRunArgs } from './args-run.mjs';
@@ -30,10 +35,7 @@ const COMMAND_PARSERS = {
30
35
  archive: (command, rest) => namedJsonCommand(command, rest),
31
36
  unarchive: (command, rest) => namedJsonCommand(command, rest),
32
37
  sessions: (command, rest) => parseSessionsArgs(command, rest),
33
- list: (command, rest) =>
34
- hasFlag(rest, '--slack-rows')
35
- ? { command, slackRows: true, name: rest.find((arg) => !arg.startsWith('--')) }
36
- : jsonOnlyCommand(command, rest),
38
+ list: (command, rest) => parseListArgs(command, rest),
37
39
  inspect: (command, rest) => namedJsonCommand(command, rest),
38
40
  tokens: (command, rest) => namedJsonCommand(command, rest),
39
41
  budget: (command, rest) => namedJsonCommand(command, rest),
@@ -47,6 +49,8 @@ const COMMAND_PARSERS = {
47
49
  root: (command, rest) => jsonOnlyCommand(command, rest),
48
50
  notes: (_command, rest) => parseNotesArgs(rest),
49
51
  skills: (command, rest) => parseSkillsArgs(command, rest),
52
+ 'set-model': (command, rest) => parseSetModelArgs(command, rest),
53
+ lead: (command, rest) => parseLeadArgs(command, rest),
50
54
  };
51
55
 
52
56
  export function parseArgs(argv) {
@@ -62,6 +66,13 @@ function hasFlag(args, flag) {
62
66
  return args.includes(flag);
63
67
  }
64
68
 
69
+ function parseListArgs(command, rest) {
70
+ const named = rest.find((arg) => !arg.startsWith('--'));
71
+ if (hasFlag(rest, '--slack-rows')) return { command, slackRows: true, name: named };
72
+ if (hasFlag(rest, '--agent-row')) return { command, agentRow: true, name: named };
73
+ return jsonOnlyCommand(command, rest);
74
+ }
75
+
65
76
  function jsonOnlyCommand(command, rest) {
66
77
  return { command, json: hasFlag(rest, '--json') };
67
78
  }
@@ -163,9 +174,22 @@ Usage: ${CLI_NAME} <command> [options]
163
174
 
164
175
  Commands:
165
176
  doctor [--json] Report Academy's version, roots, runtimes, and capability
166
- create [<name>] [--kind specialist|principal|owner] [--no-start] [--json]
177
+ create [<name>] [--kind specialist|principal|owner] [--model <id>]
178
+ [--display-name <text>] [--runtime claude-code|codex|copilot]
179
+ [--no-start] [--json]
167
180
  Scaffold a portable agent at ~/.academy/agents/<name>/ (default
168
- specialist); a terminal Principal opens its first session
181
+ specialist); a terminal Principal opens its first session;
182
+ --model stores its default model; --display-name stores a
183
+ person-facing name ("Principal Two"); <name> stays kebab-case;
184
+ --runtime stores the launch runtime, as run --agent does
185
+ set-model <name> <model> [--json]
186
+ Change an agent's default model; run passes it to the
187
+ runtime (Claude --model, Codex -m) unless the caller does
188
+ lead set <agent> --project <path> [--json]
189
+ Make <agent> the Lead of a project, replacing any
190
+ previous Lead (one per project)
191
+ lead clear --project <path> [--json]
192
+ Remove a project's Lead
169
193
  hire Interactive hire flow — produces 8 boot files via Claude Code
170
194
  hire --spec <path> [--json]
171
195
  Headless hire from a JSON specification file
@@ -184,21 +208,26 @@ Commands:
184
208
  sessions [--agent <name>] [--json]
185
209
  List sessions whose agent directory is inside
186
210
  AGENTS_ROOT
187
- rename <old> <new> [--json]
211
+ rename <old> [<new>] [--display-name <text>] [--json]
188
212
  Change an agent's name: the name: field and the seeded
189
213
  name lines. No folder moves and no job changes; safe
190
- any time, also mid-session
214
+ any time, also mid-session. --display-name sets the
215
+ display name; without <new> it changes only that
191
216
  start <name> [--mandate <file>] [--hours <h>] [--contact <c>] [--trust 1-4] [--json]
192
217
  Start an agent and register its jobs; an Owner needs a
193
218
  signed mandate and gets one first daily run
194
- stop <name> [--json] Stop an agent and delete its jobs; its files stay
219
+ stop <name> [--json] Turn off an agent's loop; it stays available and keeps
220
+ its nightly
195
221
  archive <name> [--json] Move an agent into the archived holding area
196
222
  unarchive <name> [--json]
197
223
  Restore an archived agent to its canonical slot
198
- list List all agents
224
+ list [--json] List all agents; --json records add model, created_by
225
+ (user, a creating agent's id, or null) and lead_of
199
226
  list --slack-rows [<name>]
200
227
  Print Slack launch rows {name, cwd, runtime, command}
201
228
  for every Owner and Principal, or only <name>
229
+ list --agent-row <name> Print one JSON row for any agent:
230
+ {name, cwd, runtime, stopped, draft}
202
231
  inspect <name> Inspect one agent
203
232
  tokens <name> Estimate generated prompt tokens by surface
204
233
  budget <name> Check prompt token budget by surface
@@ -212,6 +241,9 @@ Examples:
212
241
  ${CLI_NAME} doctor --json
213
242
  ${CLI_NAME} create kai
214
243
  ${CLI_NAME} create ada --kind principal --json
244
+ ${CLI_NAME} create ada --kind principal --model opus --json
245
+ ${CLI_NAME} set-model ada sonnet --json
246
+ ${CLI_NAME} lead set ada --project ~/Dev/app --json
215
247
  ${CLI_NAME} hire
216
248
  ${CLI_NAME} hire --spec ./kai.json --json
217
249
  ${CLI_NAME} run kai
@@ -222,6 +254,7 @@ Examples:
222
254
  ${CLI_NAME} migrate --dry-run --json
223
255
  ${CLI_NAME} sessions --agent kai --json
224
256
  ${CLI_NAME} rename kai nova --json
257
+ ${CLI_NAME} rename principal-two --display-name "Principal Two" --json
225
258
  ${CLI_NAME} start owner-one --mandate ./mandate.md --trust 3 --json
226
259
  ${CLI_NAME} stop owner-one --json
227
260
  ${CLI_NAME} notes add "User prefers short status updates before edits"
@@ -50,6 +50,8 @@ export function childEnvironment(env) {
50
50
  // The daemon's managed-resume launch binding must survive the child boundary.
51
51
  'TIGHTBEAM_LAUNCH_MODE',
52
52
  'TIGHTBEAM_LAUNCH_TOKEN',
53
+ // A cold start names the placeholder its SessionStart must adopt.
54
+ 'TIGHTBEAM_PLACEHOLDER_ENDPOINT_ID',
53
55
  'TLDR_AGENT_HOME',
54
56
  // Academy's generated Beads store, session actor and telemetry switch; acpx lets the
55
57
  // parent's values win over any name not listed here.