@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 +18 -8
- package/bin/introspection-operator.js +118 -0
- package/package.json +8 -4
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
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
`
|
|
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/`)
|
|
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
|
|
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 `
|
|
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.
|
|
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": "
|
|
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
|
}
|