@introspection-ai/operator 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -9,19 +9,29 @@ The Operator is the agent that builds, deploys and improves other agents (Recipe
9
9
  | `evals/` | The evals that measure each loop (to come) |
10
10
  | `docs/` | [`DESIGN.md`](docs/DESIGN.md): what the Operator is being rebuilt to, and the decisions still open |
11
11
 
12
- ## Run build locally
12
+ ## Run build
13
+
14
+ You need Node 24 and credentials for the model in `agents/build.yaml` (sign in once with `/login` in Pi).
13
15
 
14
16
  ```sh
15
- pnpm build # a new agent, in ~/.introspection/agents/new-<n>
16
- pnpm build ~/.introspection/agents/new-1 # resume that build
17
- pnpm build ~/work/my-agent --model <model> # another folder; flags after it go to Pi
17
+ npm i -g @introspection-ai/cli && introspection setup # once: Pi and the Recipes release it needs
18
+ npx @introspection-ai/operator # a new agent, in ~/.introspection/agents/new-<n>
19
+ npx @introspection-ai/operator ~/work/my-agent # start or resume a build in that folder
20
+ npx @introspection-ai/operator ~/work/my-agent --model <model> # flags after the folder go to Pi
18
21
  ```
19
22
 
20
- `scripts/build.sh` starts the build agent (`pi --recipe <this repo> --agent build --no-context-files`) in a folder of its own, because build treats the folder it starts in as the owner's workspace: it creates the agent's repository there and resumes from what it finds there. It turns off Pi's loading of AGENTS.md and CLAUDE.md from that folder and the folders above it, which are written for coding agents and can describe other agents build must not copy. It refuses a folder inside this repository or nested in another git repository, installs this repository's dependencies with pnpm once, and checks that Pi's `@introspection-ai/recipes` satisfies the `peerDependencies` range, printing the `pi install` command to run when it doesn't. Set `INTROSPECTION_AGENTS_DIR` to put new builds somewhere other than `~/.introspection/agents`. You need Pi and the `introspection` CLI (`npm i -g @introspection-ai/cli`), and credentials for the model in `agents/build.yaml`.
23
+ `npx @introspection-ai/operator` runs the package's launcher, `bin/introspection-operator.js`. It starts the build agent (`pi --recipe <the package> --agent build --no-context-files`) in a folder of its own, because build treats the folder it starts in as the owner's workspace: it creates the agent's repository there and resumes from what it finds there.
24
+ - **No AGENTS.md or CLAUDE.md:** it turns off Pi's loading of those files from that folder and the folders above it. They're written for coding agents and can describe other agents, which build must not copy.
25
+ - **Folder safety:** it refuses a folder inside the package or nested in another git repository; the agent's own repository is allowed, to resume.
26
+ - **Versions:** it checks that Pi's `@introspection-ai/recipes` satisfies the `peerDependencies` range, and says to run `introspection upgrade` when it doesn't.
27
+
28
+ Set `INTROSPECTION_AGENTS_DIR` to put new builds somewhere other than `~/.introspection/agents`.
29
+
30
+ In a checkout of this repository, `pnpm build` runs the same launcher, and installs the dependencies with pnpm once.
21
31
 
22
32
  ## Package
23
33
 
24
- `@introspection-ai/operator` is the npm package published from this repository's root. It contains the Recipe a person runs (`SYSTEM.md`, `agents/` and `skills/`), so `pi install npm:@introspection-ai/operator` then `pi --recipe @introspection-ai/operator --agent build` starts a build, and `npx skills sync` finds the skills in it. `docs/`, `scripts/` and the evals are never published.
34
+ `@introspection-ai/operator` is the npm package published from this repository's root. It contains the Recipe a person runs (`SYSTEM.md`, `agents/` and `skills/`) and its launcher (`bin/`), so `npx @introspection-ai/operator` starts a build, and `npx skills sync` finds the skills in it. `docs/`, `scripts/` and the evals are never published.
25
35
 
26
36
  Skills are grouped by the stage of an agent's life (see [`docs/DESIGN.md`](docs/DESIGN.md)), and the group is the prefix of each skill's name, because `npx skills sync` finds skills only at `skills/<name>/SKILL.md` and not in nested folders:
27
37
 
@@ -33,8 +43,8 @@ Skills are grouped by the stage of an agent's life (see [`docs/DESIGN.md`](docs/
33
43
  | `optimize` | `optimize-quality`, `optimize-cost`, `optimize-latency` | Raise quality, then cut cost, then latency, each holding the gains of the one before | Designed; replaces today's `optimize` |
34
44
  | `harden` | `harden-scan`, `harden-spar`, `harden-stress` | Last, and optional apart from the scan: check the Recipe against the OWASP Agentic Skills Top 10, spar with it as a red team, and test it under load; failures become permanent cases | Designed; new, with no workflow behind it today |
35
45
 
36
- Releases are cut by release-please (`.github/workflows/release.yml`): Conventional Commit titles on `main` update a release pull request, and merging it tags `operator-vX.Y.Z` and publishes to npm through trusted publishing, starting at 0.1.0. CI runs `node scripts/check-skills.mjs` (frontmatter, folder names, and that exactly the Recipe is packed: `skills/`, `agents/` and `SYSTEM.md`) and `npm pack --dry-run`; publishing also requires at least one skill.
46
+ Releases are cut by release-please (`.github/workflows/release.yml`): Conventional Commit titles on `main` update a release pull request, and merging it tags `operator-vX.Y.Z` and publishes to npm through trusted publishing, starting at 0.1.0. CI runs `node scripts/check-skills.mjs` (frontmatter, folder names, and that exactly the Recipe and its launcher are packed: `skills/`, `agents/`, `SYSTEM.md` and `bin/introspection-operator.js`) and `npm pack --dry-run`; publishing also requires at least one skill.
37
47
 
38
48
  ## License
39
49
 
40
- What `@introspection-ai/operator` publishes (`SYSTEM.md`, `agents/` and `skills/`) is licensed under the Apache License 2.0; see [`skills/LICENSE`](skills/LICENSE). Everything else in this repository is proprietary to Introspection and not licensed for use outside it.
50
+ What `@introspection-ai/operator` publishes (`SYSTEM.md`, `agents/`, `skills/` and `bin/`) is licensed under the Apache License 2.0; see [`skills/LICENSE`](skills/LICENSE). Everything else in this repository is proprietary to Introspection and not licensed for use outside it.
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+ // Start the Operator's build agent in a folder of its own.
3
+ //
4
+ // npx @introspection-ai/operator new build in ~/.introspection/agents/new-<n>
5
+ // npx @introspection-ai/operator <folder> start, or resume, a build in <folder>
6
+ // npx @introspection-ai/operator [<folder>] <pi flags> pass flags such as --model on to Pi
7
+ //
8
+ // In a checkout of this repository, `pnpm build` runs this same script.
9
+ //
10
+ // Build treats the folder it starts in as the owner's workspace: it creates the agent's repository
11
+ // there and resumes from what it finds, so it must not start inside this package or another repository.
12
+ import { execFileSync, spawnSync } from "node:child_process";
13
+ import { existsSync, mkdirSync, readFileSync, realpathSync } from "node:fs";
14
+ import { homedir } from "node:os";
15
+ import { delimiter, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
16
+ import { fileURLToPath } from "node:url";
17
+
18
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
19
+ const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
20
+ const agentsDir = process.env.INTROSPECTION_AGENTS_DIR || join(homedir(), ".introspection", "agents");
21
+ const piDir = process.env.PI_CODING_AGENT_DIR || join(homedir(), ".pi", "agent");
22
+
23
+ function fail(message) {
24
+ console.error(`introspection-operator: ${message}`);
25
+ process.exit(1);
26
+ }
27
+
28
+ let args = process.argv.slice(2);
29
+ if (args[0] === "--help" || args[0] === "-h") {
30
+ console.log(readFileSync(fileURLToPath(import.meta.url), "utf8").split("\n").slice(1, 8).map((l) => l.replace(/^\/\/ ?/, "")).join("\n"));
31
+ process.exit(0);
32
+ }
33
+ if (args[0] === "--") args = args.slice(1); // pnpm passes a separating `--` through
34
+
35
+ // A package manager puts this package's node_modules/.bin first on PATH, and the Recipes package installs
36
+ // its own Pi there, one without the Recipe extension. Use the Pi the owner installed.
37
+ const ownBin = join(root, "node_modules", ".bin");
38
+ const path = (process.env.PATH || "").split(delimiter).filter((dir) => resolve(dir) !== ownBin).join(delimiter);
39
+ const env = { ...process.env, PATH: path };
40
+ const has = (cmd) => spawnSync(cmd, ["--version"], { env, stdio: "ignore", shell: process.platform === "win32" }).status === 0;
41
+ if (!has("pi")) fail("Pi is not installed. Run: npm i -g @introspection-ai/cli && introspection setup");
42
+
43
+ // The Recipe's own dependencies (the documents tool) load from this package by Node's resolution, which
44
+ // also finds them where npx and npm hoist them, beside the package. A fresh checkout installs them once.
45
+ // Search node_modules folders upward from the package, as Node does; resolving `<name>/package.json`
46
+ // would fail for a package whose exports don't list it.
47
+ const resolvable = (name) => {
48
+ for (let dir = root; ; dir = dirname(dir)) {
49
+ if (existsSync(join(dir, "node_modules", name, "package.json"))) return true;
50
+ if (dirname(dir) === dir) return false;
51
+ }
52
+ };
53
+ if (!Object.keys(pkg.dependencies ?? {}).every(resolvable)) {
54
+ if (!existsSync(join(root, "pnpm-lock.yaml"))) fail(`can't find the Operator's dependencies from ${root}; install it with npm or npx`);
55
+ console.error("introspection-operator: installing the Operator's dependencies (once)");
56
+ execFileSync("pnpm", ["install", "--frozen-lockfile", "--reporter=silent"], { cwd: root, env, stdio: "inherit", shell: process.platform === "win32" });
57
+ }
58
+
59
+ // Pi loads the Recipe through its own copy of @introspection-ai/recipes, which must satisfy the peer range.
60
+ const range = pkg.peerDependencies?.["@introspection-ai/recipes"] ?? "";
61
+ const min = /^>=\s*(\d+\.\d+\.\d+)/.exec(range)?.[1];
62
+ const piRecipes = join(piDir, "npm", "node_modules", "@introspection-ai", "recipes", "package.json");
63
+ if (min) {
64
+ if (!existsSync(piRecipes)) fail("Pi has no @introspection-ai/recipes package. Run: introspection setup");
65
+ const have = JSON.parse(readFileSync(piRecipes, "utf8")).version;
66
+ const num = (v) => v.split(/[.-]/).slice(0, 3).map(Number);
67
+ const [a, b] = [num(have), num(min)];
68
+ if ((a[0] - b[0] || a[1] - b[1] || a[2] - b[2]) < 0) {
69
+ fail(`Pi uses @introspection-ai/recipes ${have}; the Operator needs ${range}. Run: introspection upgrade`);
70
+ }
71
+ }
72
+
73
+ // The folder: the first argument when it isn't a flag, else the next new-<n>. A relative folder resolves
74
+ // from where the command was typed (pnpm runs scripts from the package root, so INIT_CWD first).
75
+ const typedFrom = process.env.INIT_CWD || process.cwd();
76
+ let target;
77
+ if (args.length > 0 && !args[0].startsWith("-")) {
78
+ target = resolve(typedFrom, args.shift());
79
+ } else {
80
+ let n = 1;
81
+ while (existsSync(join(agentsDir, `new-${n}`))) n++;
82
+ target = join(agentsDir, `new-${n}`);
83
+ }
84
+
85
+ // Check the folder before creating anything, from its nearest existing parent.
86
+ let existing = target;
87
+ while (!existsSync(existing)) existing = dirname(existing);
88
+ existing = realpathSync(existing);
89
+ const inside = (parent, child) => {
90
+ const rel = relative(parent, child);
91
+ return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
92
+ };
93
+ if (inside(realpathSync(root), existing)) fail(`${target} is inside the Operator package; choose a folder outside it`);
94
+ // A folder inside another repository would nest the agent's repository there. The agent's own repository
95
+ // is fine: that's how a build resumes.
96
+ let top;
97
+ try {
98
+ top = execFileSync("git", ["-C", existing, "rev-parse", "--show-toplevel"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
99
+ } catch {}
100
+ if (top) {
101
+ top = realpathSync(top);
102
+ if (!existsSync(target) || realpathSync(target) !== top) {
103
+ fail(`${target} is inside the git repository ${top}; choose a folder outside it, or the agent's own repository to resume`);
104
+ }
105
+ }
106
+
107
+ mkdirSync(target, { recursive: true });
108
+ target = realpathSync(target);
109
+ console.error(`introspection-operator: starting in ${target}`);
110
+ // Pi would load AGENTS.md and CLAUDE.md from this folder and every folder above it. Those are written for
111
+ // coding agents and can describe other agents, which build must not copy; the Recipe brings its own.
112
+ const result = spawnSync("pi", ["--recipe", root, "--agent", "build", "--no-context-files", ...args], {
113
+ cwd: target,
114
+ env,
115
+ stdio: "inherit",
116
+ shell: process.platform === "win32",
117
+ });
118
+ process.exit(result.status ?? 1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@introspection-ai/operator",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "The Introspection Operator: a Recipe and skills that build an agent with its owner (brainstorm, draft and review).",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -18,7 +18,8 @@
18
18
  "files": [
19
19
  "skills",
20
20
  "agents",
21
- "SYSTEM.md"
21
+ "SYSTEM.md",
22
+ "bin"
22
23
  ],
23
24
  "publishConfig": {
24
25
  "access": "public"
@@ -35,7 +36,7 @@
35
36
  },
36
37
  "type": "module",
37
38
  "scripts": {
38
- "build": "scripts/build.sh"
39
+ "build": "node bin/introspection-operator.js"
39
40
  },
40
41
  "peerDependencies": {
41
42
  "@introspection-ai/recipes": ">=0.37.0"
@@ -44,5 +45,8 @@
44
45
  "@introspection-ai/recipe-documents-docx": ">=0.1.0",
45
46
  "@introspection-ai/recipe-documents-pdf": ">=0.1.0"
46
47
  },
47
- "packageManager": "pnpm@12.9.1"
48
+ "packageManager": "pnpm@12.9.1",
49
+ "bin": {
50
+ "introspection-operator": "bin/introspection-operator.js"
51
+ }
48
52
  }