@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.
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +150 -0
- package/README.md +7 -4
- package/package.json +2 -2
- package/scripts/cli/agent-gate.mjs +20 -10
- package/scripts/cli/agent-row.mjs +24 -0
- package/scripts/cli/agent-settings.mjs +104 -0
- package/scripts/cli/args-kinds.mjs +59 -8
- package/scripts/cli/args-owner.mjs +9 -5
- package/scripts/cli/args.mjs +44 -11
- package/scripts/cli/copilot/child-env.mjs +2 -0
- package/scripts/cli/core.mjs +8 -7
- package/scripts/cli/create-options.mjs +43 -0
- package/scripts/cli/create.mjs +21 -41
- package/scripts/cli/curator-feed-observations.mjs +38 -4
- package/scripts/cli/curator-feed-sources.mjs +9 -10
- package/scripts/cli/doctor.mjs +3 -0
- package/scripts/cli/heartbeat.mjs +9 -39
- package/scripts/cli/inspect.mjs +6 -1
- package/scripts/cli/leads.mjs +94 -0
- package/scripts/cli/lifecycle.mjs +3 -0
- package/scripts/cli/main.mjs +6 -1
- package/scripts/cli/model.mjs +45 -0
- package/scripts/cli/nightly.mjs +46 -16
- package/scripts/cli/owner-jobs.mjs +23 -4
- package/scripts/cli/owner-sessions.mjs +5 -5
- package/scripts/cli/rename.mjs +55 -17
- package/scripts/cli/run.mjs +3 -2
- package/scripts/cli/scaffold.mjs +12 -6
- package/scripts/cli/skill-resource-paths.mjs +140 -0
- package/scripts/cli/skill-resources.mjs +158 -0
- package/scripts/cli/skills.mjs +13 -53
- package/scripts/cli/start.mjs +21 -7
- package/scripts/cli/yaml.mjs +10 -0
- package/skills/hire/references/owner.md +6 -4
- package/templates/agents/claude-code/worker.md +1 -0
- package/templates/agents/codex/worker.toml +1 -0
- package/templates/operating-model/base.md +31 -11
- package/templates/operating-model/owner-loop-off.md +9 -0
- package/templates/operating-model/owner.md +3 -2
- package/templates/packs/owner/craft.md +2 -2
- package/templates/packs/owner/mandate.md +3 -0
- package/templates/packs/owner/owner/SKILL.md +31 -26
- package/templates/packs/owner/owner-feedback/SKILL.md +4 -3
- package/templates/packs/owner/owner-heartbeat/SKILL.md +5 -20
- package/templates/packs/owner/owner-nightly/SKILL.md +10 -17
- package/templates/packs/owner/owner-review/SKILL.md +5 -4
- package/templates/packs/owner/owner-work/SKILL.md +28 -19
- package/templates/packs/principal/academy-performance-review/SKILL.md +91 -0
- package/templates/packs/principal/academy-performance-review/references/operating-standard.md +59 -0
- package/templates/packs/principal/principal-brief/SKILL.md +23 -7
- package/templates/packs/principal/principal-nightly/SKILL.md +6 -7
- package/templates/skills/delivery/SKILL.md +18 -0
- package/templates/skills/delivery/references/build.md +15 -0
- package/templates/skills/delivery/references/experience.md +12 -0
- package/templates/skills/delivery/references/orchestration.md +15 -0
- package/templates/skills/delivery/references/plan.md +15 -0
- package/templates/skills/delivery/references/run.md +26 -0
- package/templates/skills/delivery/references/scope.md +15 -0
- package/templates/skills/delivery/references/ship.md +12 -0
- package/templates/skills/delivery/references/validate.md +16 -0
- package/templates/skills/explore-options/SKILL.md +66 -0
- package/templates/skills/knowledge-curation/SKILL.md +2 -3
- package/templates/skills/nightly-consolidation/SKILL.md +9 -4
- package/templates/skills/skill-method/SKILL.md +2 -2
- package/templates/skills/triage/SKILL.md +119 -0
- package/templates/skills/triage/references/curator.md +14 -0
- package/templates/skills/triage/references/rank.md +39 -0
- package/templates/skills/user-nightly/SKILL.md +18 -10
- package/templates/skills/work-graph/SKILL.md +6 -3
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
|
-
|
|
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>] [--
|
|
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] #
|
|
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] #
|
|
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.
|
|
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.
|
|
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
|
|
10
|
-
//
|
|
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:
|
|
37
|
+
// in this state: only the nightly when stopped or for a draft Owner.
|
|
36
38
|
export function liveJobIds(dir, ids) {
|
|
37
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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>] [--
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
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 (
|
|
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
|
-
|
|
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`,
|
|
2
|
-
// option Academy does not know stays on the command
|
|
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
|
-
|
|
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 = {
|
package/scripts/cli/args.mjs
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
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] [--
|
|
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]
|
|
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
|
|
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.
|