@joenandez/academy 0.5.17-rc.1 → 0.5.17

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 +158 -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 +43 -5
  9. package/scripts/cli/args-owner.mjs +9 -5
  10. package/scripts/cli/args.mjs +42 -11
  11. package/scripts/cli/copilot/child-env.mjs +2 -0
  12. package/scripts/cli/core.mjs +8 -7
  13. package/scripts/cli/create.mjs +28 -16
  14. package/scripts/cli/curator-feed-observations.mjs +38 -4
  15. package/scripts/cli/curator-feed-sources.mjs +9 -10
  16. package/scripts/cli/doctor.mjs +3 -0
  17. package/scripts/cli/heartbeat.mjs +9 -39
  18. package/scripts/cli/inspect.mjs +6 -1
  19. package/scripts/cli/leads.mjs +94 -0
  20. package/scripts/cli/lifecycle.mjs +3 -0
  21. package/scripts/cli/main.mjs +6 -1
  22. package/scripts/cli/model.mjs +45 -0
  23. package/scripts/cli/nightly.mjs +46 -16
  24. package/scripts/cli/owner-jobs.mjs +23 -4
  25. package/scripts/cli/owner-sessions.mjs +5 -5
  26. package/scripts/cli/rename.mjs +55 -17
  27. package/scripts/cli/run.mjs +3 -2
  28. package/scripts/cli/scaffold.mjs +11 -5
  29. package/scripts/cli/skill-resource-paths.mjs +140 -0
  30. package/scripts/cli/skill-resources.mjs +158 -0
  31. package/scripts/cli/skills.mjs +13 -53
  32. package/scripts/cli/start.mjs +21 -7
  33. package/scripts/cli/tldr.mjs +2 -1
  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.1",
4
+ "version": "0.5.17",
5
5
  "hooks": "./hooks/hooks.json"
6
6
  }
package/CHANGELOG.md CHANGED
@@ -3,6 +3,164 @@
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.17 — 2026-11-11
7
+
8
+ This release collects the 0.5.17 release candidates (rc.1 to rc.6, below) and
9
+ the work merged after rc.6. npm `latest` was 0.5.13 before this release; the
10
+ 0.5.14 to 0.5.16 notes below ship with it.
11
+
12
+ ### Added
13
+
14
+ - **`tldr-agents` and `backstory` commands.** A global install puts
15
+ `academy`, `tldr-agents`, and `backstory` on your PATH. `tldr-agents` runs
16
+ the same tldr that Academy uses.
17
+ - **Delivery for every agent kind.** One skill links Scope, Plan, Build,
18
+ Validate, and Ship, with worker delegation, TDD, risk-based review, and
19
+ resumable execution checkpoints. Skills can bundle linked Markdown
20
+ references.
21
+ - **Work triage and a ranked priority list.** A `triage` skill gives each Beads
22
+ item one structured state. Owners and Principals keep a ranked priority
23
+ list, triage checks it daily, and Principals sync it to the user's Workstream
24
+ order. Agents search Beads before they file and report status from Beads
25
+ only.
26
+ - **Academy Operating Standard.** New `explore-options` and
27
+ `academy-performance-review` skills.
28
+ - **A per-agent default model, Leads, and `--display-name`.** `academy create`
29
+ and `academy rename` take `--display-name`; an agent can have a default
30
+ model and Leads.
31
+ - **`academy list --agent-row <name>`** reports one agent's row, including a
32
+ draft Owner.
33
+ - **Owner cadence.** Owners size work to the mandate's cadence, recover due
34
+ check-ins without a nightly run, and keep reviews within cadence while work
35
+ waits. The first Owner welcome does not depend on cadence.
36
+ - **The Principal morning brief goes to every channel** the user set up and
37
+ covers the Principal's own work and queue. The user-nightly learns the
38
+ user's preferences.
39
+
40
+ ### Changed
41
+
42
+ - **One install command.** Install with `npm install -g @joenandez/academy`,
43
+ then run `tldr-agents setup`. tldr comes from the npm registry, pinned to
44
+ `@joenandez/tldr` 0.3.0-rc.3; Academy no longer bundles it. If you installed
45
+ `@joenandez/tldr` globally, uninstall it first.
46
+ - **Stopping an agent turns off its work loop.** Nightly consolidation stays
47
+ scheduled. An Owner heartbeat only restarts stopped work.
48
+ - **Nightly consolidation waits for 10 new observations.** Smaller inputs carry
49
+ over to a later run.
50
+ - **Nightly and Owner daily jobs have a late-start window**, as does the
51
+ Principal morning brief.
52
+ - **Subagent guidance.** Agents use workers for bounded work and handle small
53
+ checks themselves.
54
+ - Each Workstream names its production version and release cycle (`Prod:`).
55
+
56
+ ### Fixed
57
+
58
+ - **`npm install -g` no longer fails on macOS.** The registry dependency
59
+ installs with default npm settings.
60
+ - **Copilot setup on macOS without the Claude Code tldr plugin.** Academy
61
+ skips the runtime-only copy and uses the npm package. The Copilot pre-launch
62
+ error names `tldr-agents setup`.
63
+ - A one-run runtime switch (`--runtime-once`) launches without the stored
64
+ model.
65
+ - Owner stopped-claim recovery, the Copilot cold-start placeholder id, a
66
+ late-swept observation file, and stopped-agent loop checks (identified by
67
+ scheduler job ID).
68
+ - The triage backfill acts only where the live state matches its backup.
69
+
70
+ ### Removed
71
+
72
+ - **The bundled tldr** (`vendor/tldr`).
73
+
74
+ ### Install / upgrade
75
+
76
+ - Run `npm install -g @joenandez/academy`, then run `tldr-agents setup` to
77
+ replace any old Copilot hooks record.
78
+
79
+ ## 0.5.17-rc.6 — 2026-10-28
80
+
81
+ ### Fixed
82
+
83
+ - Keep delivery's internal resource helpers private so the package passes the
84
+ required dead-code gate. This replaces the local rc.5 candidate; neither RC
85
+ was published to npm or the public repository.
86
+
87
+ ## 0.5.17-rc.5 — 2026-10-28
88
+
89
+ ### Added
90
+
91
+ - **Delivery for every agent kind.** One compact skill links Scope, Plan, Build,
92
+ Validate, and Ship. Agents load only the stage they need. It includes worker
93
+ delegation, TDD, product validation, risk-based adversarial review, durable
94
+ feature records, and resumable execution checkpoints.
95
+ - **Bundled skill references.** Agent creation and launch refresh install linked
96
+ Markdown references on both runtime skill surfaces. Refresh preserves unknown
97
+ files and reports ownership conflicts before writing.
98
+
99
+ ### Changed
100
+
101
+ - **Stopping an agent turns off its work loop.** Nightly consolidation remains
102
+ scheduled. A stopped Owner receives the loop-off operating model and can still
103
+ respond to on-demand work.
104
+ - **Nightly consolidation waits for 20 new observations.** Smaller inputs carry
105
+ over to a later run.
106
+ - **Owner heartbeats only restart stopped work.** A heartbeat no longer starts a
107
+ session for idle ready work; the Owner paces itself with booked sessions and
108
+ the daily open. Reverts the idle start from #4.
109
+
110
+ ### Fixed
111
+
112
+ - A late-swept observation file still counts as waiting nightly input.
113
+ - Stopped-agent checks identify loop runs by scheduler job ID, so they do not
114
+ block unrelated on-demand sessions.
115
+
116
+ ## 0.5.17-rc.4 — 2026-10-28
117
+
118
+ ### Added
119
+
120
+ - **Work triage.** A new `triage` skill for every agent kind. Each Beads item
121
+ has one structured state: untriaged, triaged, next, now, blocked, waiting on
122
+ the user's decision (a human gate), PR open, merged but not released,
123
+ parked, or done with proof. Principals triage before the morning brief, and
124
+ Owners at the start of the daily open. Each nightly makes statuses true, and
125
+ the curator cleans up duplicates and items with no owner or Workstream. On
126
+ its first run, each agent drafts its own ranking criteria for the user to
127
+ confirm and converts its open items.
128
+ - **Capture, status, and surfacing rules** in every session. Agents search
129
+ Beads before they file and add to a match. They report status from Beads
130
+ only, and show related open items when the user discusses an area.
131
+
132
+ ### Changed
133
+
134
+ - Each Workstream names its production version and release cycle (`Prod:`).
135
+ - New Owner tasks take their Workstream's `owner:` label.
136
+
137
+ ## 0.5.17-rc.3 — 2026-10-27
138
+
139
+ ### Changed
140
+
141
+ - **Subagent guidance.** Agents use workers for bounded work and handle small
142
+ checks themselves. They check the model and capacity pool, pass context and
143
+ evidence requirements, run independent tasks in parallel, and verify the
144
+ result. Owners delegate substantial execution and handle small checks.
145
+
146
+ ## 0.5.17-rc.2 — 2026-10-27
147
+
148
+ ### Fixed
149
+
150
+ - **Copilot setup on macOS without the Claude Code tldr plugin.** Academy
151
+ skips the runtime-only copy that setup leaves behind and uses the npm
152
+ package. The tldr command and Copilot hooks use a runnable installation.
153
+
154
+ ### Changed
155
+
156
+ - **tldr 0.3.0-rc.2.** Academy pins this exact version. It fixes Copilot hook
157
+ registration on macOS and replaces the record made by tldr 0.3.0-rc.1.
158
+
159
+ ### Install / upgrade
160
+
161
+ - Run `npm install -g @joenandez/academy@next`, then run
162
+ `tldr-agents setup` to replace the old Copilot hooks record.
163
+
6
164
  ## 0.5.17-rc.1 — 2026-10-27
7
165
 
8
166
  ### Added
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>] [--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.1",
3
+ "version": "0.5.17",
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.1",
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,11 +1,14 @@
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>] [--no-start]
2
+ // [--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.
5
6
  export function parseCreateArgs(command, rest) {
6
7
  const json = rest.includes('--json');
7
8
  let name;
8
9
  let kind;
10
+ let model;
11
+ let displayName;
9
12
  let noStart = false;
10
13
  for (let i = 0; i < rest.length; i++) {
11
14
  const arg = rest[i];
@@ -13,10 +16,14 @@ export function parseCreateArgs(command, rest) {
13
16
  else if (arg === '--no-start') noStart = true;
14
17
  else if (arg === '--kind') kind = rest[++i] ?? '';
15
18
  else if (arg.startsWith('--kind=')) kind = arg.slice('--kind='.length);
19
+ else if (arg === '--model') model = rest[++i] ?? '';
20
+ else if (arg.startsWith('--model=')) model = arg.slice('--model='.length);
21
+ else if (arg === '--display-name') displayName = rest[++i] ?? '';
22
+ else if (arg.startsWith('--display-name=')) displayName = arg.slice('--display-name='.length);
16
23
  else if (arg.startsWith('--')) return { command, json, name, invalidOption: arg };
17
24
  else name ??= arg;
18
25
  }
19
- return { command, json, name, kind, noStart };
26
+ return { command, json, name, kind, model, displayName, noStart };
20
27
  }
21
28
 
22
29
  // `skills check <agent> [--json]`. Like `create`, an option Academy does not
@@ -31,3 +38,34 @@ export function parseSkillsArgs(command, rest) {
31
38
  }
32
39
  return { command, json, action: positional[0], name: positional[1] };
33
40
  }
41
+
42
+ // `set-model <name> <model> [--json]`. The model stays a raw string, so the
43
+ // handler answers a missing or unusable one in the envelope.
44
+ export function parseSetModelArgs(command, rest) {
45
+ const json = rest.includes('--json');
46
+ const positional = [];
47
+ for (const arg of rest) {
48
+ if (arg === '--json') continue;
49
+ else if (arg.startsWith('--')) return { command, json, invalidOption: arg };
50
+ else positional.push(arg);
51
+ }
52
+ return { command, json, name: positional[0], model: positional[1] };
53
+ }
54
+
55
+ // `lead set <agent> --project <path> [--json]` and `lead clear --project <path> [--json]`.
56
+ export function parseLeadArgs(command, rest) {
57
+ const json = rest.includes('--json');
58
+ const positional = [];
59
+ let project;
60
+ for (let i = 0; i < rest.length; i++) {
61
+ const arg = rest[i];
62
+ if (arg === '--json') continue;
63
+ else if (arg === '--project') project = rest[++i] ?? '';
64
+ else if (arg.startsWith('--project=')) project = arg.slice('--project='.length);
65
+ else if (arg.startsWith('--')) return { command, json, invalidOption: arg };
66
+ else positional.push(arg);
67
+ }
68
+ const [action, name, extra] = positional;
69
+ if (extra !== undefined) return { command, json, invalidOption: extra };
70
+ return { command, json, action, name, project };
71
+ }
@@ -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,20 @@ 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>] [--no-start] [--json]
167
179
  Scaffold a portable agent at ~/.academy/agents/<name>/ (default
168
- specialist); a terminal Principal opens its first session
180
+ specialist); a terminal Principal opens its first session;
181
+ --model stores its default model; --display-name stores a
182
+ person-facing name ("Principal Two"); <name> stays kebab-case
183
+ set-model <name> <model> [--json]
184
+ Change an agent's default model; run passes it to the
185
+ runtime (Claude --model, Codex -m) unless the caller does
186
+ lead set <agent> --project <path> [--json]
187
+ Make <agent> the Lead of a project, replacing any
188
+ previous Lead (one per project)
189
+ lead clear --project <path> [--json]
190
+ Remove a project's Lead
169
191
  hire Interactive hire flow — produces 8 boot files via Claude Code
170
192
  hire --spec <path> [--json]
171
193
  Headless hire from a JSON specification file
@@ -184,21 +206,26 @@ Commands:
184
206
  sessions [--agent <name>] [--json]
185
207
  List sessions whose agent directory is inside
186
208
  AGENTS_ROOT
187
- rename <old> <new> [--json]
209
+ rename <old> [<new>] [--display-name <text>] [--json]
188
210
  Change an agent's name: the name: field and the seeded
189
211
  name lines. No folder moves and no job changes; safe
190
- any time, also mid-session
212
+ any time, also mid-session. --display-name sets the
213
+ display name; without <new> it changes only that
191
214
  start <name> [--mandate <file>] [--hours <h>] [--contact <c>] [--trust 1-4] [--json]
192
215
  Start an agent and register its jobs; an Owner needs a
193
216
  signed mandate and gets one first daily run
194
- stop <name> [--json] Stop an agent and delete its jobs; its files stay
217
+ stop <name> [--json] Turn off an agent's loop; it stays available and keeps
218
+ its nightly
195
219
  archive <name> [--json] Move an agent into the archived holding area
196
220
  unarchive <name> [--json]
197
221
  Restore an archived agent to its canonical slot
198
- list List all agents
222
+ list [--json] List all agents; --json records add model, created_by
223
+ (user, a creating agent's id, or null) and lead_of
199
224
  list --slack-rows [<name>]
200
225
  Print Slack launch rows {name, cwd, runtime, command}
201
226
  for every Owner and Principal, or only <name>
227
+ list --agent-row <name> Print one JSON row for any agent:
228
+ {name, cwd, runtime, stopped, draft}
202
229
  inspect <name> Inspect one agent
203
230
  tokens <name> Estimate generated prompt tokens by surface
204
231
  budget <name> Check prompt token budget by surface
@@ -212,6 +239,9 @@ Examples:
212
239
  ${CLI_NAME} doctor --json
213
240
  ${CLI_NAME} create kai
214
241
  ${CLI_NAME} create ada --kind principal --json
242
+ ${CLI_NAME} create ada --kind principal --model opus --json
243
+ ${CLI_NAME} set-model ada sonnet --json
244
+ ${CLI_NAME} lead set ada --project ~/Dev/app --json
215
245
  ${CLI_NAME} hire
216
246
  ${CLI_NAME} hire --spec ./kai.json --json
217
247
  ${CLI_NAME} run kai
@@ -222,6 +252,7 @@ Examples:
222
252
  ${CLI_NAME} migrate --dry-run --json
223
253
  ${CLI_NAME} sessions --agent kai --json
224
254
  ${CLI_NAME} rename kai nova --json
255
+ ${CLI_NAME} rename principal-two --display-name "Principal Two" --json
225
256
  ${CLI_NAME} start owner-one --mandate ./mandate.md --trust 3 --json
226
257
  ${CLI_NAME} stop owner-one --json
227
258
  ${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.