@cohortapp/agent-sdk 2.3.2 → 2.4.0
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/framework-features.json +30 -0
- package/lib/backlog.mjs +136 -0
- package/lib/cadences.mjs +63 -2
- package/lib/cadences.test.mjs +105 -0
- package/lib/capability/inventory.mjs +542 -0
- package/lib/capability/inventory.test.mjs +232 -0
- package/lib/capability/probe.mjs +255 -0
- package/lib/channels/contract.mjs +37 -1
- package/lib/channels/contract.test.mjs +25 -1
- package/lib/claude-bin.mjs +37 -3
- package/lib/claude-bin.test.mjs +42 -8
- package/lib/execution/disposition.mjs +501 -0
- package/lib/execution/disposition.test.mjs +482 -0
- package/lib/execution/drive.mjs +352 -0
- package/lib/execution/drive.test.mjs +270 -0
- package/lib/execution/effects.mjs +340 -0
- package/lib/execution/effects.test.mjs +193 -0
- package/lib/execution/index.mjs +152 -0
- package/lib/execution/intake.mjs +581 -0
- package/lib/execution/intake.test.mjs +343 -0
- package/lib/execution/journal.mjs +374 -0
- package/lib/execution/journal.test.mjs +261 -0
- package/lib/execution/match.mjs +331 -0
- package/lib/execution/match.test.mjs +235 -0
- package/lib/execution/pipeline.mjs +341 -0
- package/lib/execution/pipeline.test.mjs +389 -0
- package/lib/execution/route.mjs +332 -0
- package/lib/execution/route.test.mjs +186 -0
- package/lib/execution/surface-policy.mjs +446 -0
- package/lib/execution/surface-policy.test.mjs +162 -0
- package/lib/goals/admission.mjs +209 -0
- package/lib/goals/admission.test.mjs +139 -0
- package/lib/goals/classify.mjs +206 -0
- package/lib/goals/classify.test.mjs +109 -0
- package/lib/goals/collaborate.mjs +415 -0
- package/lib/goals/collaborate.test.mjs +324 -0
- package/lib/goals/gaps.mjs +111 -0
- package/lib/goals/gaps.test.mjs +284 -0
- package/lib/goals/loop.mjs +537 -0
- package/lib/goals/loop.test.mjs +719 -0
- package/lib/identity/persona.mjs +247 -0
- package/lib/identity/persona.test.mjs +117 -0
- package/lib/kpi.mjs +469 -0
- package/lib/kpi.test.mjs +244 -0
- package/lib/mandate/audit.mjs +168 -0
- package/lib/mandate/audit.test.mjs +195 -0
- package/lib/mandate/cache.mjs +162 -0
- package/lib/mandate/derive.mjs +317 -0
- package/lib/mandate/derive.test.mjs +224 -0
- package/lib/mandate/model.mjs +352 -0
- package/lib/mandate/model.test.mjs +145 -0
- package/lib/mandate/refresh.mjs +187 -0
- package/lib/mandate/refresh.test.mjs +293 -0
- package/lib/mcp/server.test.mjs +4 -4
- package/lib/org/approvals.mjs +14 -2
- package/lib/org/client.mjs +58 -22
- package/lib/org/client.test.mjs +3 -1
- package/lib/org/inbound/directedness.mjs +720 -0
- package/lib/org/inbound/directedness.test.mjs +543 -0
- package/lib/org/inbound/facts.mjs +501 -0
- package/lib/org/inbound/facts.test.mjs +375 -0
- package/lib/org/inbound/hydrate.mjs +535 -0
- package/lib/org/inbound/hydrate.test.mjs +326 -0
- package/lib/org/inbound/index.mjs +233 -0
- package/lib/org/inbound/index.test.mjs +324 -0
- package/lib/org/inbound/io.mjs +141 -0
- package/lib/org/inbound/project.mjs +201 -0
- package/lib/org/inbound/project.test.mjs +287 -0
- package/lib/org/inbound/surfaces.mjs +257 -0
- package/lib/org/knowledge.mjs +10 -1
- package/lib/org/knowledge.test.mjs +8 -1
- package/lib/org/leases.mjs +5 -0
- package/lib/org/mesh.mjs +17 -2
- package/lib/org/messaging.mjs +40 -4
- package/lib/org/messaging.test.mjs +40 -0
- package/lib/org/param-contract.mjs +694 -0
- package/lib/org/param-contract.test.mjs +451 -0
- package/lib/org/protocol.checksum +1 -1
- package/lib/org/protocol.mjs +8 -0
- package/lib/org/protocol.test.mjs +5 -1
- package/lib/org/push.mjs +1025 -0
- package/lib/org/push.test.mjs +690 -0
- package/lib/org/tool-surface.mjs +138 -38
- package/lib/org/tool-surface.test.mjs +13 -8
- package/lib/org/typing.mjs +341 -0
- package/lib/org/typing.test.mjs +291 -0
- package/lib/plan/compile.mjs +510 -0
- package/lib/plan/compile.test.mjs +286 -0
- package/lib/plan/emit.mjs +256 -0
- package/lib/plan/emit.test.mjs +246 -0
- package/lib/plan/explain.mjs +226 -0
- package/lib/plan/explain.test.mjs +188 -0
- package/lib/plan/schema.mjs +140 -0
- package/lib/resource-governor.mjs +47 -1
- package/lib/resource-governor.test.mjs +21 -1
- package/lib/setup/enroll-from-cohort.mjs +84 -16
- package/lib/setup/enroll-from-cohort.test.mjs +43 -1
- package/lib/setup/sections/identity.mjs +15 -4
- package/lib/setup/sections/identity.test.mjs +94 -0
- package/lib/setup/sections/inventory.mjs +178 -0
- package/lib/setup/sections/inventory.test.mjs +198 -0
- package/lib/setup/sections/mandate.mjs +392 -0
- package/lib/setup/sections/mandate.test.mjs +373 -0
- package/lib/setup/sections/subagents.mjs +427 -0
- package/lib/setup/sections/subagents.test.mjs +429 -0
- package/lib/setup/sections/verify.mjs +121 -0
- package/lib/setup/sections/verify.test.mjs +175 -0
- package/lib/setup/sot.mjs +2 -0
- package/lib/subagents/cli.mjs +463 -0
- package/lib/subagents/cli.test.mjs +389 -0
- package/lib/subagents/client.mjs +373 -0
- package/lib/subagents/client.test.mjs +309 -0
- package/lib/subagents/gap.mjs +268 -0
- package/lib/subagents/gap.test.mjs +234 -0
- package/lib/subagents/lock.mjs +296 -0
- package/lib/subagents/lock.test.mjs +248 -0
- package/lib/subagents/manifest.mjs +224 -0
- package/lib/subagents/manifest.test.mjs +175 -0
- package/lib/subagents/refs.mjs +274 -0
- package/lib/subagents/refs.test.mjs +204 -0
- package/lib/subagents/resolve.mjs +455 -0
- package/lib/subagents/resolve.test.mjs +422 -0
- package/lib/subagents/schema.mjs +467 -0
- package/lib/subagents/schema.test.mjs +306 -0
- package/package.json +8 -3
- package/plugins/maestro-skills/.claude-plugin/marketplace.json +16 -0
- package/policies/ai-disclosure.yaml +42 -2
- package/scaffold/CLAUDE.md +16 -2
- package/schedules/triggers/goal-steward.md +79 -0
- package/scripts/ci/conformance-org-api.mjs +792 -0
- package/scripts/ci/conformance-org-api.test.mjs +417 -0
- package/scripts/daemon/agent-daemon.mjs +36 -4
- package/scripts/daemon/cadence-handlers.mjs +145 -1
- package/scripts/daemon/goal-steward-cadence.test.mjs +243 -0
- package/scripts/daemon/inbox-deferral.mjs +45 -2
- package/scripts/daemon/inbox-deferral.test.mjs +56 -0
- package/scripts/daemon/inbox-wake.mjs +282 -0
- package/scripts/daemon/inbox-wake.test.mjs +199 -0
- package/scripts/daemon/prompt-builder.mjs +41 -1
- package/scripts/daemon/typing-registry.mjs +55 -2
- package/scripts/daemon/typing-registry.test.mjs +25 -0
- package/scripts/local-triggers/generate-plists.test.mjs +5 -5
- package/scripts/setup/gen-subagent-manifest.mjs +95 -0
- package/scripts/setup/gen-subagent-manifest.test.mjs +124 -0
- package/scripts/setup/generate-plan.mjs +108 -0
- package/scripts/setup/init-capability-manifest.mjs +70 -0
- package/scripts/setup/init-skill-marketplace.mjs +155 -0
- package/scripts/setup/init-skill-marketplace.test.mjs +193 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* schema.test.mjs — the frontmatter parser/validator (Sub-Agent Registry §2.1).
|
|
3
|
+
* Run: node --test lib/subagents/schema.test.mjs
|
|
4
|
+
*/
|
|
5
|
+
"use strict";
|
|
6
|
+
|
|
7
|
+
import { test } from "node:test";
|
|
8
|
+
import assert from "node:assert/strict";
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
AUTHORED_KEYS,
|
|
15
|
+
MODEL_ALIASES,
|
|
16
|
+
PROVENANCE_KEYS,
|
|
17
|
+
bodyTokens,
|
|
18
|
+
checkAgentFile,
|
|
19
|
+
hashBody,
|
|
20
|
+
hashFile,
|
|
21
|
+
normaliseModel,
|
|
22
|
+
parseAgentMd,
|
|
23
|
+
parseFrontmatter,
|
|
24
|
+
splitFrontmatter,
|
|
25
|
+
stampProvenance,
|
|
26
|
+
stringifyAgentMd,
|
|
27
|
+
stringifyFrontmatter,
|
|
28
|
+
stripProvenance,
|
|
29
|
+
unsupportedTokens,
|
|
30
|
+
validateAgentMd,
|
|
31
|
+
} from "./schema.mjs";
|
|
32
|
+
import { listAgentDirs } from "./manifest.mjs";
|
|
33
|
+
|
|
34
|
+
const MAESTRO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
35
|
+
|
|
36
|
+
const GOOD = [
|
|
37
|
+
"---",
|
|
38
|
+
"name: alpha",
|
|
39
|
+
"description: Handles alpha work.",
|
|
40
|
+
"model: claude-sonnet-4-6",
|
|
41
|
+
'tools: ["Read", "Write"]',
|
|
42
|
+
"---",
|
|
43
|
+
"# alpha",
|
|
44
|
+
"",
|
|
45
|
+
"Serve {{agent.firstName}}.",
|
|
46
|
+
"",
|
|
47
|
+
].join("\n");
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// splitFrontmatter
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
test("splitFrontmatter: splits fences and keeps the body verbatim", () => {
|
|
54
|
+
const s = splitFrontmatter(GOOD);
|
|
55
|
+
assert.equal(s.ok, true);
|
|
56
|
+
assert.match(s.frontmatterText, /^name: alpha\n/);
|
|
57
|
+
assert.match(s.body, /^# alpha\n/);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("splitFrontmatter: a prose-only file is an ERROR, not empty frontmatter", () => {
|
|
61
|
+
// The bug this closes: an agents/<slug>/ with no real frontmatter used to pass
|
|
62
|
+
// doctor because doctor only checked that the directory existed.
|
|
63
|
+
const s = splitFrontmatter("# alpha\n\njust prose\n");
|
|
64
|
+
assert.equal(s.ok, false);
|
|
65
|
+
assert.match(s.error, /missing opening/);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("splitFrontmatter: unterminated frontmatter is an error", () => {
|
|
69
|
+
const s = splitFrontmatter("---\nname: alpha\n# no closing fence\n");
|
|
70
|
+
assert.equal(s.ok, false);
|
|
71
|
+
assert.match(s.error, /unterminated/);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("splitFrontmatter: tolerates a BOM and CRLF line endings", () => {
|
|
75
|
+
const crlf = "" + GOOD.replace(/\n/g, "\r\n");
|
|
76
|
+
const s = splitFrontmatter(crlf);
|
|
77
|
+
assert.equal(s.ok, true);
|
|
78
|
+
assert.equal(s.body.includes("\r"), false);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("splitFrontmatter: empty frontmatter block parses to an empty map", () => {
|
|
82
|
+
const s = splitFrontmatter("---\n---\nbody\n");
|
|
83
|
+
assert.equal(s.ok, true);
|
|
84
|
+
assert.equal(s.frontmatterText, "");
|
|
85
|
+
assert.equal(s.body, "body\n");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// parseFrontmatter
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
test("parseFrontmatter: scalars, flow sequences and block sequences", () => {
|
|
93
|
+
const r = parseFrontmatter([
|
|
94
|
+
"name: alpha",
|
|
95
|
+
'description: "Handles: alpha work"',
|
|
96
|
+
"model: sonnet # trailing comment",
|
|
97
|
+
'tools: [Read, "Write", \'Edit\']',
|
|
98
|
+
"tokens:",
|
|
99
|
+
" - agent.firstName",
|
|
100
|
+
" - agent.company",
|
|
101
|
+
].join("\n"));
|
|
102
|
+
assert.equal(r.ok, true, r.errors.join("; "));
|
|
103
|
+
assert.equal(r.value.name, "alpha");
|
|
104
|
+
assert.equal(r.value.description, "Handles: alpha work");
|
|
105
|
+
assert.equal(r.value.model, "sonnet");
|
|
106
|
+
assert.deepEqual(r.value.tools, ["Read", "Write", "Edit"]);
|
|
107
|
+
assert.deepEqual(r.value.tokens, ["agent.firstName", "agent.company"]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("parseFrontmatter: a `#` inside a quoted scalar is content, not a comment", () => {
|
|
111
|
+
const r = parseFrontmatter('description: "ships #1 priority"');
|
|
112
|
+
assert.equal(r.value.description, "ships #1 priority");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("parseFrontmatter: a comma inside a quoted flow item is not a separator", () => {
|
|
116
|
+
// The registry round-trips these bytes into a database — "unlikely" is not a
|
|
117
|
+
// licence to corrupt.
|
|
118
|
+
const r = parseFrontmatter('tools: ["Read, maybe", Write]');
|
|
119
|
+
assert.deepEqual(r.value.tools, ["Read, maybe", "Write"]);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("parseFrontmatter: anything outside the closed grammar is an ERROR, never a silent mis-parse", () => {
|
|
123
|
+
const r = parseFrontmatter("name: alpha\nnested:\n key: value\n");
|
|
124
|
+
assert.equal(r.ok, false);
|
|
125
|
+
assert.match(r.errors.join(" "), /nested maps are outside the frontmatter grammar/);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("parseFrontmatter: an orphan list item is an error", () => {
|
|
129
|
+
const r = parseFrontmatter("name: alpha\n - stray\n");
|
|
130
|
+
assert.equal(r.ok, false);
|
|
131
|
+
assert.match(r.errors.join(" "), /no owning key/);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("parseFrontmatter: an unterminated flow sequence is an error", () => {
|
|
135
|
+
const r = parseFrontmatter("tools: [Read, Write\n");
|
|
136
|
+
assert.equal(r.ok, false);
|
|
137
|
+
assert.match(r.errors.join(" "), /unterminated flow sequence/);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// model normalisation — where the shipped-vs-generated drift dies
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
test("normaliseModel: aliases and full ids both normalise, idempotently", () => {
|
|
145
|
+
assert.equal(normaliseModel("sonnet"), "claude-sonnet-4-6");
|
|
146
|
+
assert.equal(normaliseModel("claude-sonnet-4-6"), "claude-sonnet-4-6");
|
|
147
|
+
assert.equal(normaliseModel(normaliseModel("opus")), "claude-opus-4-8");
|
|
148
|
+
assert.equal(normaliseModel("HAIKU"), "claude-haiku-4-5");
|
|
149
|
+
assert.equal(normaliseModel("inherit"), "inherit");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("normaliseModel: unknown → null (an error for the caller), absent → \"\"", () => {
|
|
153
|
+
assert.equal(normaliseModel("gpt-4o"), null);
|
|
154
|
+
assert.equal(normaliseModel(""), "");
|
|
155
|
+
assert.equal(normaliseModel(undefined), "");
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("MODEL_ALIASES: every alias target is itself a key (normalisation is a fixpoint)", () => {
|
|
159
|
+
for (const target of Object.values(MODEL_ALIASES)) {
|
|
160
|
+
assert.equal(MODEL_ALIASES[target], target, `${target} must map to itself`);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
// validate
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
function fm(over = {}) {
|
|
169
|
+
return { name: "alpha", description: "d", model: "sonnet", tools: ["Read"], ...over };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
test("validateAgentMd: the happy path normalises the model and derives tokens", () => {
|
|
173
|
+
const v = validateAgentMd({ frontmatter: fm(), body: "x {{agent.firstName}}", slug: "alpha" });
|
|
174
|
+
assert.equal(v.ok, true, v.errors.join("; "));
|
|
175
|
+
assert.equal(v.normalised.frontmatter.model, "claude-sonnet-4-6");
|
|
176
|
+
assert.deepEqual(v.normalised.frontmatter.tokens, ["agent.firstName"]);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("validateAgentMd: name must equal the directory name (identity IS the dirname)", () => {
|
|
180
|
+
const v = validateAgentMd({ frontmatter: fm({ name: "beta" }), body: "x", slug: "alpha" });
|
|
181
|
+
assert.equal(v.ok, false);
|
|
182
|
+
assert.match(v.errors.join(" "), /must equal the directory name/);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("validateAgentMd: description, model, tools and body are all required", () => {
|
|
186
|
+
const cases = [
|
|
187
|
+
[fm({ description: "" }), "x", /description is required/],
|
|
188
|
+
[fm({ model: "gpt-4o" }), "x", /not a known model/],
|
|
189
|
+
[fm({ model: "" }), "x", /model is required/],
|
|
190
|
+
[fm({ tools: undefined }), "x", /tools is required/],
|
|
191
|
+
[fm({ tools: [] }), "x", /tools must be non-empty/],
|
|
192
|
+
[fm(), " ", /body is empty/],
|
|
193
|
+
];
|
|
194
|
+
for (const [f, body, re] of cases) {
|
|
195
|
+
const v = validateAgentMd({ frontmatter: f, body, slug: "alpha" });
|
|
196
|
+
assert.equal(v.ok, false, `expected failure for ${re}`);
|
|
197
|
+
assert.match(v.errors.join(" "), re);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("validateAgentMd: an ABSENT tokens key is derived, not an error", () => {
|
|
202
|
+
// Every file that shipped before the registry existed has no `tokens:` key.
|
|
203
|
+
// Demanding one would fail-closed the whole fleet on the first doctor run.
|
|
204
|
+
const v = validateAgentMd({ frontmatter: fm(), body: "{{agent.company}}", slug: "alpha" });
|
|
205
|
+
assert.equal(v.ok, true);
|
|
206
|
+
assert.deepEqual(v.normalised.frontmatter.tokens, ["agent.company"]);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("validateAgentMd: a DECLARED tokens list must cover every token the body uses", () => {
|
|
210
|
+
const v = validateAgentMd({ frontmatter: fm({ tokens: [] }), body: "{{agent.company}}", slug: "alpha" });
|
|
211
|
+
assert.equal(v.ok, false);
|
|
212
|
+
assert.match(v.errors.join(" "), /undeclared token/);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("validateAgentMd: declared-but-unused and renderer-unknown tokens WARN, never fail", () => {
|
|
216
|
+
const v = validateAgentMd({
|
|
217
|
+
frontmatter: fm({ tokens: ["agent.firstName", "agent.nope"] }),
|
|
218
|
+
body: "{{agent.firstName}}",
|
|
219
|
+
slug: "alpha",
|
|
220
|
+
});
|
|
221
|
+
assert.equal(v.ok, true, v.errors.join("; "));
|
|
222
|
+
assert.match(v.warnings.join(" "), /declared but unused/);
|
|
223
|
+
assert.match(v.warnings.join(" "), /unknown to this agent's renderer/);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("unsupportedTokens: honours an injected KNOWN_TOKENS set", () => {
|
|
227
|
+
assert.deepEqual(unsupportedTokens(["a", "b"], new Set(["a"])), ["b"]);
|
|
228
|
+
assert.deepEqual(unsupportedTokens(null, new Set()), []);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("bodyTokens: deduped and sorted", () => {
|
|
232
|
+
assert.deepEqual(bodyTokens("{{agent.z}} {{agent.a}} {{agent.z}}"), ["agent.a", "agent.z"]);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
// emit / round-trip — what makes local-edit detection trustworthy
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
|
|
239
|
+
test("stringifyFrontmatter: canonical key order (authored, then provenance, then rest)", () => {
|
|
240
|
+
const text = stringifyFrontmatter({ contentHash: "h", extra: 1, tools: ["Read"], name: "alpha" });
|
|
241
|
+
assert.deepEqual(text.trim().split("\n"), ['name: alpha', 'tools: ["Read"]', "contentHash: h", "extra: 1"]);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("round trip: parse(stringify(x)) === x, and a second pass is byte-identical", () => {
|
|
245
|
+
const f = { name: "alpha", description: "d", model: "claude-sonnet-4-6", tools: ["Read", "Write"], tokens: [] };
|
|
246
|
+
const once = stringifyAgentMd(f, "# alpha\n\nbody\n");
|
|
247
|
+
const parsed = parseAgentMd(once);
|
|
248
|
+
assert.equal(parsed.ok, true, parsed.errors.join("; "));
|
|
249
|
+
assert.deepEqual(parsed.frontmatter, f);
|
|
250
|
+
assert.equal(stringifyAgentMd(parsed.frontmatter, parsed.body), once);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test("stampProvenance: is IDEMPOTENT — re-stamping produces identical bytes", () => {
|
|
254
|
+
// Non-idempotent stamping would change the file hash on every materialisation
|
|
255
|
+
// and make local-edit detection permanently wrong.
|
|
256
|
+
const one = stampProvenance({
|
|
257
|
+
frontmatter: fm(), body: "b\n", layer: "workspace", source: "cohort://o/alpha", version: 7,
|
|
258
|
+
});
|
|
259
|
+
const re = parseAgentMd(one);
|
|
260
|
+
const two = stampProvenance({
|
|
261
|
+
frontmatter: re.frontmatter, body: re.body, layer: "workspace", source: "cohort://o/alpha", version: 7,
|
|
262
|
+
});
|
|
263
|
+
assert.equal(two, one);
|
|
264
|
+
assert.equal(hashFile(two), hashFile(one));
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test("stampProvenance: never renders tokens — {{agent.*}} survives to disk", () => {
|
|
268
|
+
const text = stampProvenance({ frontmatter: fm(), body: "hi {{agent.firstName}}\n", layer: "sdk" });
|
|
269
|
+
assert.match(text, /\{\{agent\.firstName\}\}/);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("stripProvenance: removes exactly the registry-owned keys", () => {
|
|
273
|
+
const out = stripProvenance({ name: "alpha", layer: "sdk", source: "s", version: "1", contentHash: "h" });
|
|
274
|
+
assert.deepEqual(Object.keys(out), ["name"]);
|
|
275
|
+
for (const k of PROVENANCE_KEYS) assert.equal(k in out, false);
|
|
276
|
+
assert.ok(AUTHORED_KEYS.includes("name"));
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test("hashBody and hashFile have different preimages (body vs whole file)", () => {
|
|
280
|
+
const parsed = parseAgentMd(GOOD);
|
|
281
|
+
assert.notEqual(hashBody(parsed.body), hashFile(GOOD));
|
|
282
|
+
assert.equal(hashBody(parsed.body), hashBody(parsed.body));
|
|
283
|
+
assert.equal(hashBody(null), hashBody(""));
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
// the real corpus — the validator must not reject what already shipped
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
|
|
290
|
+
test("every shipped agents/*/agent.md validates (the fleet must not fail-closed)", () => {
|
|
291
|
+
const { slugs } = listAgentDirs(MAESTRO_ROOT);
|
|
292
|
+
assert.ok(slugs.length >= 1, "expected the SDK to ship at least one agent");
|
|
293
|
+
const bad = [];
|
|
294
|
+
for (const slug of slugs) {
|
|
295
|
+
const text = readFileSync(join(MAESTRO_ROOT, "agents", slug, "agent.md"), "utf8");
|
|
296
|
+
const c = checkAgentFile(text, slug);
|
|
297
|
+
if (!c.ok) bad.push(`${slug}: ${c.errors.join(", ")}`);
|
|
298
|
+
}
|
|
299
|
+
assert.deepEqual(bad, []);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test("checkAgentFile: reports the parse error rather than throwing", () => {
|
|
303
|
+
const c = checkAgentFile("no frontmatter here", "alpha");
|
|
304
|
+
assert.equal(c.ok, false);
|
|
305
|
+
assert.match(c.errors.join(" "), /missing opening/);
|
|
306
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cohortapp/agent-sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Cohort Agent SDK \u2014 autonomous AI colleague runtime. Deploy senior AI colleagues on dedicated Mac minis, wired to the Cohort operating surface.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -36,6 +36,9 @@
|
|
|
36
36
|
"./org/approvals": "./lib/org/approvals.mjs",
|
|
37
37
|
"./org/mesh": "./lib/org/mesh.mjs",
|
|
38
38
|
"./org/cohort-client": "./lib/org/cohort-client.mjs",
|
|
39
|
+
"./org/inbound": "./lib/org/inbound/index.mjs",
|
|
40
|
+
"./org/inbound/surfaces": "./lib/org/inbound/surfaces.mjs",
|
|
41
|
+
"./org/inbound/directedness": "./lib/org/inbound/directedness.mjs",
|
|
39
42
|
"./secrets/broker": "./lib/secrets/broker.mjs",
|
|
40
43
|
"./secrets/providers": "./lib/secrets/providers.mjs",
|
|
41
44
|
"./diagnostics/trace": "./lib/diagnostics/trace.mjs",
|
|
@@ -118,9 +121,11 @@
|
|
|
118
121
|
"scripts": {
|
|
119
122
|
"test": "node scripts/ci/run-tests.mjs",
|
|
120
123
|
"test:coverage": "node scripts/ci/run-coverage.mjs",
|
|
121
|
-
"test:list": "node --test bin/maestro.test.mjs lib/action-executor.test.mjs lib/archetype.test.mjs lib/autonomy.test.mjs lib/backlog.test.mjs lib/budget-guard.test.mjs lib/cadence-bus-schedule.test.mjs lib/cadence-bus.test.mjs lib/cadences.test.mjs lib/capability.test.mjs lib/channels/base-adapter.test.mjs lib/channels/channels.test.mjs lib/channels/contract.test.mjs lib/channels/inbox-item.test.mjs lib/channels/pairing.test.mjs lib/channels/slack-adapter.test.mjs lib/channels/telegram-adapter.test.mjs lib/channels/whatsapp/baileys-typing.test.mjs lib/charter.test.mjs lib/claude-bin.test.mjs lib/collective/capture.test.mjs lib/collective/cards.test.mjs lib/collective/config.test.mjs lib/collective/global-config.test.mjs lib/collective/presence.test.mjs lib/collective/recall.test.mjs lib/comms.test.mjs lib/fs-atomic.test.mjs lib/fs-ownership.test.mjs lib/learning/config.test.mjs lib/learning/counters.test.mjs lib/learning/curator-consolidate.test.mjs lib/learning/curator.test.mjs lib/learning/reflect.test.mjs lib/learning/session-index.test.mjs lib/learning/skill-writer.test.mjs lib/model-router.test.mjs lib/model-router/catalog.test.mjs lib/model-router/economics.test.mjs lib/model-router/failover.test.mjs lib/model-router/health.test.mjs lib/model-router/integration.test.mjs lib/model-router/integration-coverage.test.mjs lib/model-router/ledger.test.mjs lib/model-router/llm-task.test.mjs lib/model-router/org-credentials.test.mjs lib/model-router/reconcile.test.mjs lib/model-router/repair.test.mjs lib/model-router/spawn.test.mjs lib/model-router/taxonomy.test.mjs lib/org/approvals.test.mjs lib/org/board.test.mjs lib/org/client.test.mjs lib/org/cost-sync.test.mjs lib/org/handoff.test.mjs lib/org/keys.test.mjs lib/org/knowledge.test.mjs lib/org/activity.test.mjs lib/org/leases.test.mjs lib/org/mesh.test.mjs lib/org/messaging.test.mjs lib/org/cohort-client.test.mjs lib/org/policy.test.mjs lib/org/registry.test.mjs lib/org/tool-surface.test.mjs lib/org/ui-parity.test.mjs lib/org/doctor.test.mjs lib/org/verify.test.mjs lib/mcp/server.test.mjs lib/channels/orgmail/adapter.test.mjs lib/rag/rag.test.mjs lib/rate-guard.test.mjs lib/render.test.mjs lib/resource-governor.test.mjs lib/security/coerce-args.test.mjs lib/security/dangerous-tools.test.mjs lib/security/external-content.test.mjs lib/security/redact.test.mjs lib/security/secret-equal.test.mjs lib/session-permissions.test.mjs lib/setup/completeness.test.mjs lib/setup/context-pack.test.mjs lib/setup/enrich.test.mjs lib/setup/enroll-from-cohort.test.mjs lib/setup/integration.test.mjs lib/setup/io.test.mjs lib/setup/runner.test.mjs lib/setup/sections/identity.test.mjs lib/setup/sections/learning.test.mjs lib/setup/sections/messaging.test.mjs lib/setup/sections/org.test.mjs lib/setup/sections/orgmail.test.mjs lib/setup/sections/recovery.test.mjs lib/setup/sot.test.mjs lib/setup/state.test.mjs lib/singleton.test.mjs lib/telemetry/alerts.test.mjs lib/telemetry/collect.test.mjs lib/tool-definitions.test.mjs lib/voice/outbound.test.mjs lib/voice/stt.test.mjs lib/voice/voice.test.mjs scripts/cadence/enqueue-cadence-tick.test.mjs scripts/ci/check-no-build-artifacts.test.mjs scripts/ci/check-no-residual-identity.test.mjs scripts/ci/check.test.mjs scripts/collective/hook-runner.test.mjs scripts/cost/fleet-digest.test.mjs scripts/daemon/cadence-consumer-governance.test.mjs scripts/daemon/cadence-consumer.test.mjs scripts/daemon/cadence-handlers.test.mjs scripts/daemon/classify-kind.test.mjs scripts/daemon/dispatcher-cooldown.test.mjs scripts/daemon/dispatcher-governance.test.mjs scripts/daemon/inbox-deferral.test.mjs scripts/daemon/lib/session-router.test.mjs scripts/daemon/prompt-builder.test.mjs scripts/daemon/typing-registry.test.mjs scripts/hooks/pre-send-audit.test.mjs scripts/local-triggers/generate-plists.test.mjs scripts/poller/inbox-scan-poller.test.mjs scripts/poller/lib/cloud-relay-dedup.test.mjs scripts/poller/slack-socket-mode.test.mjs scripts/setup/init-agent.test.mjs services/webhook-relay/server.test.mjs",
|
|
122
|
-
"test:setup": "node --test lib/setup/io.test.mjs lib/setup/state.test.mjs lib/setup/sot.test.mjs lib/setup/completeness.test.mjs lib/setup/context-pack.test.mjs lib/setup/enrich.test.mjs lib/setup/runner.test.mjs lib/setup/integration.test.mjs",
|
|
124
|
+
"test:list": "node --test bin/maestro.test.mjs lib/action-executor.test.mjs lib/archetype.test.mjs lib/autonomy.test.mjs lib/backlog.test.mjs lib/budget-guard.test.mjs lib/cadence-bus-schedule.test.mjs lib/cadence-bus.test.mjs lib/cadences.test.mjs lib/capability.test.mjs lib/channels/base-adapter.test.mjs lib/channels/channels.test.mjs lib/channels/contract.test.mjs lib/channels/inbox-item.test.mjs lib/channels/pairing.test.mjs lib/channels/slack-adapter.test.mjs lib/channels/telegram-adapter.test.mjs lib/channels/whatsapp/baileys-typing.test.mjs lib/charter.test.mjs lib/claude-bin.test.mjs lib/collective/capture.test.mjs lib/collective/cards.test.mjs lib/collective/config.test.mjs lib/collective/global-config.test.mjs lib/collective/presence.test.mjs lib/collective/recall.test.mjs lib/comms.test.mjs lib/fs-atomic.test.mjs lib/fs-ownership.test.mjs lib/learning/config.test.mjs lib/learning/counters.test.mjs lib/learning/curator-consolidate.test.mjs lib/learning/curator.test.mjs lib/learning/reflect.test.mjs lib/learning/session-index.test.mjs lib/learning/skill-writer.test.mjs lib/model-router.test.mjs lib/model-router/catalog.test.mjs lib/model-router/economics.test.mjs lib/model-router/failover.test.mjs lib/model-router/health.test.mjs lib/model-router/integration.test.mjs lib/model-router/integration-coverage.test.mjs lib/model-router/ledger.test.mjs lib/model-router/llm-task.test.mjs lib/model-router/org-credentials.test.mjs lib/model-router/reconcile.test.mjs lib/model-router/repair.test.mjs lib/model-router/spawn.test.mjs lib/model-router/taxonomy.test.mjs lib/org/approvals.test.mjs lib/org/board.test.mjs lib/org/client.test.mjs lib/org/cost-sync.test.mjs lib/org/handoff.test.mjs lib/org/keys.test.mjs lib/org/knowledge.test.mjs lib/org/activity.test.mjs lib/org/leases.test.mjs lib/org/mesh.test.mjs lib/org/messaging.test.mjs lib/org/cohort-client.test.mjs lib/org/policy.test.mjs lib/org/registry.test.mjs lib/org/tool-surface.test.mjs lib/org/ui-parity.test.mjs lib/org/doctor.test.mjs lib/org/verify.test.mjs lib/mcp/server.test.mjs lib/channels/orgmail/adapter.test.mjs lib/rag/rag.test.mjs lib/rate-guard.test.mjs lib/render.test.mjs lib/resource-governor.test.mjs lib/security/coerce-args.test.mjs lib/security/dangerous-tools.test.mjs lib/security/external-content.test.mjs lib/security/redact.test.mjs lib/security/secret-equal.test.mjs lib/session-permissions.test.mjs lib/setup/completeness.test.mjs lib/setup/context-pack.test.mjs lib/setup/enrich.test.mjs lib/setup/enroll-from-cohort.test.mjs lib/setup/integration.test.mjs lib/setup/io.test.mjs lib/setup/runner.test.mjs lib/setup/sections/identity.test.mjs lib/setup/sections/learning.test.mjs lib/setup/sections/messaging.test.mjs lib/setup/sections/org.test.mjs lib/setup/sections/orgmail.test.mjs lib/setup/sections/recovery.test.mjs lib/setup/sections/subagents.test.mjs lib/setup/sot.test.mjs lib/setup/state.test.mjs lib/singleton.test.mjs lib/subagents/schema.test.mjs lib/subagents/refs.test.mjs lib/subagents/manifest.test.mjs lib/subagents/lock.test.mjs lib/subagents/resolve.test.mjs lib/subagents/gap.test.mjs lib/subagents/client.test.mjs lib/subagents/cli.test.mjs lib/telemetry/alerts.test.mjs lib/telemetry/collect.test.mjs lib/tool-definitions.test.mjs lib/voice/outbound.test.mjs lib/voice/stt.test.mjs lib/voice/voice.test.mjs scripts/cadence/enqueue-cadence-tick.test.mjs scripts/ci/check-no-build-artifacts.test.mjs scripts/ci/check-no-residual-identity.test.mjs scripts/ci/check.test.mjs scripts/collective/hook-runner.test.mjs scripts/cost/fleet-digest.test.mjs scripts/daemon/cadence-consumer-governance.test.mjs scripts/daemon/cadence-consumer.test.mjs scripts/daemon/cadence-handlers.test.mjs scripts/daemon/classify-kind.test.mjs scripts/daemon/dispatcher-cooldown.test.mjs scripts/daemon/dispatcher-governance.test.mjs scripts/daemon/inbox-deferral.test.mjs scripts/daemon/lib/session-router.test.mjs scripts/daemon/prompt-builder.test.mjs scripts/daemon/typing-registry.test.mjs scripts/hooks/pre-send-audit.test.mjs scripts/local-triggers/generate-plists.test.mjs scripts/poller/inbox-scan-poller.test.mjs scripts/poller/lib/cloud-relay-dedup.test.mjs scripts/poller/slack-socket-mode.test.mjs scripts/setup/gen-subagent-manifest.test.mjs scripts/setup/init-agent.test.mjs services/webhook-relay/server.test.mjs",
|
|
125
|
+
"test:setup": "node --test lib/setup/io.test.mjs lib/setup/state.test.mjs lib/setup/sot.test.mjs lib/setup/completeness.test.mjs lib/setup/context-pack.test.mjs lib/setup/enrich.test.mjs lib/setup/runner.test.mjs lib/setup/integration.test.mjs lib/setup/sections/subagents.test.mjs",
|
|
123
126
|
"check": "node scripts/ci/check.mjs",
|
|
127
|
+
"conformance:org": "node scripts/ci/conformance-org-api.mjs",
|
|
128
|
+
"conformance:org:write": "node scripts/ci/conformance-org-api.mjs --write",
|
|
124
129
|
"test:router": "node --test lib/model-router.test.mjs lib/model-router/catalog.test.mjs lib/model-router/economics.test.mjs lib/model-router/failover.test.mjs lib/model-router/health.test.mjs lib/model-router/integration.test.mjs lib/model-router/integration-coverage.test.mjs lib/model-router/ledger.test.mjs lib/model-router/llm-task.test.mjs lib/model-router/org-credentials.test.mjs lib/model-router/reconcile.test.mjs lib/model-router/repair.test.mjs lib/model-router/spawn.test.mjs lib/model-router/taxonomy.test.mjs scripts/cost/fleet-digest.test.mjs",
|
|
125
130
|
"test:voice": "node --test lib/voice/voice.test.mjs lib/voice/outbound.test.mjs",
|
|
126
131
|
"test:channels": "node --test lib/channels/channels.test.mjs",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "maestro-skills",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "maestro"
|
|
5
|
+
},
|
|
6
|
+
"metadata": {
|
|
7
|
+
"description": "The maestro framework skill pack. Registering this marketplace is what makes the skill files in plugins/maestro-skills/skills/ actually invocable — without it they are inert markdown and rung 1 of the execution ladder (\"a skill exists for this procedure\") is a lie. lib/capability/inventory.mjs probes for this file and reports every skill as reachable:false when it is absent."
|
|
8
|
+
},
|
|
9
|
+
"plugins": [
|
|
10
|
+
{
|
|
11
|
+
"name": "maestro-skills",
|
|
12
|
+
"source": "./",
|
|
13
|
+
"description": "Maestro operational skills — executive briefings, communications, hiring, strategy, org-desk workflows (mail, files, calendar, CRM, books, directory, design) and Cohort call working sessions."
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -154,9 +154,30 @@ channels:
|
|
|
154
154
|
footer: >
|
|
155
155
|
Sent by {agent_name}, an AI assistant working on behalf of
|
|
156
156
|
{principal_name}.
|
|
157
|
+
# WHEN THE HUMAN-READABLE FOOTER IS APPENDED.
|
|
158
|
+
# Previously unscoped, which meant it rendered on EVERY email — including
|
|
159
|
+
# replies deep in a thread and mail to colleagues inside the deploying
|
|
160
|
+
# organisation who provisioned the agent in the first place. That is not
|
|
161
|
+
# what Art. 50(1) asks for (it addresses informing a person that they are
|
|
162
|
+
# interacting with an AI system, which a continuing correspondent already
|
|
163
|
+
# knows), and the standing repetition reads as the agent disclaiming its own
|
|
164
|
+
# work rather than as a transparency control.
|
|
165
|
+
#
|
|
166
|
+
# The X- header is UNCONDITIONAL and carries the machine-readable duty under
|
|
167
|
+
# Art. 50(2) on every message. Only the human-readable footer is scoped.
|
|
168
|
+
footer_scope:
|
|
169
|
+
first_contact_only: true # not on replies within an established thread
|
|
170
|
+
external_recipients_only: true # not to members of the deploying org
|
|
171
|
+
# A recipient is "internal" when they resolve to a member of this agent's
|
|
172
|
+
# own organisation (Cohort directory / config/contacts.yaml). Anyone else
|
|
173
|
+
# is external and gets the footer on first contact.
|
|
174
|
+
internal_recipient_source: ["cohort_directory", "config/contacts.yaml"]
|
|
157
175
|
notes: >
|
|
158
|
-
The X- header is the machine-readable marking
|
|
159
|
-
human-readable one
|
|
176
|
+
The X- header is the machine-readable marking and always ships. The footer
|
|
177
|
+
is the human-readable one and is scoped by `footer_scope` — first contact,
|
|
178
|
+
external recipients. Keep it one line, no emoji. Scoping the footer does not
|
|
179
|
+
touch the truthfulness invariant: a sincere question about the agent's
|
|
180
|
+
nature is answered plainly on any message, first or thousandth.
|
|
160
181
|
|
|
161
182
|
whatsapp:
|
|
162
183
|
identity_line: >
|
|
@@ -268,6 +289,25 @@ jurisdictions:
|
|
|
268
289
|
- "Disclose AI use in defined employment and biometric contexts."
|
|
269
290
|
machine_readable_marking_required: false
|
|
270
291
|
|
|
292
|
+
AE:
|
|
293
|
+
law: "UAE / DIFC — no enumerated AI-transparency statute as at 2026-08."
|
|
294
|
+
status: unlegislated
|
|
295
|
+
# DELIBERATELY fail-safe, and NOT a considered position. Until counsel rules,
|
|
296
|
+
# a UAE-seated deployment inherits the strictest posture. Named explicitly
|
|
297
|
+
# rather than left to fall through to `default:` so the choice is visible to
|
|
298
|
+
# whoever reviews this file — a deployment headquartered here was silently
|
|
299
|
+
# being governed by the catch-all and nobody could see it.
|
|
300
|
+
#
|
|
301
|
+
# TO REVIEW (governance.owner_role): whether a UAE-seated agent corresponding
|
|
302
|
+
# with UAE and non-EU counterparties should sit at disclose-on-ask instead.
|
|
303
|
+
# Note this is about the PROACTIVE posture only; the truthfulness invariant is
|
|
304
|
+
# locked and unaffected either way.
|
|
305
|
+
posture: disclose-always
|
|
306
|
+
machine_readable_marking_required: false
|
|
307
|
+
note: >
|
|
308
|
+
Reaching an EU-based recipient pulls the EU entry in regardless of where the
|
|
309
|
+
agent is seated — apply the strictest of the two, per the header above.
|
|
310
|
+
|
|
271
311
|
# Catch-all for any jurisdiction not enumerated above.
|
|
272
312
|
default:
|
|
273
313
|
law: "No enumerated statute; deployment default."
|
package/scaffold/CLAUDE.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
## Identity
|
|
4
4
|
|
|
5
|
-
You are
|
|
5
|
+
You are `{{agent.fullName}}`, `{{agent.title}}` at `{{agent.company}}`, reporting to `{{agent.principal.fullName}}`, `{{agent.principal.title}}`. Your background and remit are in `{{agent.persona}}`, `{{agent.responsibilities}}` and `{{agent.operatingPrinciples}}`; your register is `{{agent.communication.defaultTone}}` internally and `{{agent.communication.externalTone}}` outside the company. All of these resolve from `config/agent.json`, which the Cohort enrolment pull keeps in sync with your organisation's record of you.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
If those tokens are still unresolved, your identity has not been configured yet — run the deterministic setup wizard:
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
maestro setup
|
|
@@ -12,6 +12,14 @@ maestro setup
|
|
|
12
12
|
|
|
13
13
|
This configures your name, role, responsibilities, company context, model/provider, channels, the full operating model (charter, backlog, cadences, policies), and self-verifies. It is resumable (checkpointed) and keeps `config/agent.json` as the single source of truth. (`claude "/init-maestro"` remains as a manual LLM-driven fallback.)
|
|
14
14
|
|
|
15
|
+
### How you write and carry yourself
|
|
16
|
+
|
|
17
|
+
You hold this role. Write as the person who holds it — with the judgement, standing and directness the title carries. You are a full member of this team, not a service attached to it.
|
|
18
|
+
|
|
19
|
+
Do not introduce, describe or sign yourself as an assistant, a bot, an AI helper, or as "working on behalf of" someone. You report to `{{agent.principal.firstName}}` the way any senior colleague reports to their manager, and you do not preface your work with that fact. Do not position yourself as junior to whoever you are writing to, including your principal or the CEO — state what you did, what you found, and what you recommend, and ask for a decision only when the decision is genuinely theirs. Don't thank people for their patience, apologise for taking their time, or hedge a finding you're confident in.
|
|
20
|
+
|
|
21
|
+
Being straight about what you are is not negotiable and doesn't conflict with any of the above: if someone sincerely asks whether they're dealing with an AI, say so plainly and without drama. That's a direct answer to a direct question, not a standing disclaimer you attach to your own work. `policies/ai-disclosure.yaml` is the authority on where proactive disclosure is legally required; it is not a licence to narrate your own nature unprompted.
|
|
22
|
+
|
|
15
23
|
## Source of Truth & Identity Tokens
|
|
16
24
|
|
|
17
25
|
Your canonical identity lives in `config/agent.json`. Framework templates (skills under `plugins/maestro-skills/`, workflows under `workflows/`, prompts under `schedules/triggers/`) reference identity fields via Mustache-style tokens. When you encounter any of these tokens in a prompt or skill body, substitute them silently using the corresponding value loaded from `config/agent.json` (and `config/company.json` for `{{company.*}}` tokens):
|
|
@@ -30,6 +38,12 @@ Your canonical identity lives in `config/agent.json`. Framework templates (skill
|
|
|
30
38
|
| `{{agent.principal.title}}` | Your principal's title |
|
|
31
39
|
| `{{agent.principal.email}}` | Your principal's email |
|
|
32
40
|
| `{{agent.repoSlug}}` | Your agent repo slug (used in paths) |
|
|
41
|
+
| `{{agent.persona}}` | Your background / bio, as the org records it |
|
|
42
|
+
| `{{agent.responsibilities}}` | What you own (list) |
|
|
43
|
+
| `{{agent.operatingPrinciples}}` | How you operate (list) |
|
|
44
|
+
| `{{agent.altitude}}` | Your seniority band (founder … senior-manager) |
|
|
45
|
+
| `{{agent.communication.defaultTone}}` | Your internal register |
|
|
46
|
+
| `{{agent.communication.externalTone}}` | Your register outside the company |
|
|
33
47
|
| `{{company.name}}` | Your company's name (from `config/company.json`) |
|
|
34
48
|
| `{{company.website}}` | Your company's website |
|
|
35
49
|
| `{{company.industry}}` | Your company's industry |
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
You are {{agent.fullName}}, {{agent.title}} to {{agent.principal.fullName}}, {{agent.principal.title}} of {{agent.company}}.
|
|
2
|
+
|
|
3
|
+
## Workflow: Goal Steward — close the gap between what you are accountable for and what is actually happening
|
|
4
|
+
|
|
5
|
+
## Cadence: Daily 07:15
|
|
6
|
+
|
|
7
|
+
## Working Directory: the agent repo root (your current working directory)
|
|
8
|
+
|
|
9
|
+
You are only reading this because the deterministic loop already ran and could
|
|
10
|
+
not serve an open gap on its own. It has already measured, computed the gap,
|
|
11
|
+
consulted the trend, and applied every admission gate. Your job is the one part
|
|
12
|
+
it cannot do: **propose the concrete moves that would actually close the gap.**
|
|
13
|
+
|
|
14
|
+
### What the loop already did
|
|
15
|
+
|
|
16
|
+
Read, in this order:
|
|
17
|
+
|
|
18
|
+
1. `state/mandate/cache.json` — your adopted mandate. PILLAR → OBJECTIVE → GOAL.
|
|
19
|
+
Only `state: active` objectives drive work. A `proposed` objective drives
|
|
20
|
+
nothing; if something important is only proposed, say so, do not act on it.
|
|
21
|
+
2. `state/kpi/measurements.jsonl` — the measured series per objective.
|
|
22
|
+
3. `state/kpi/interventions.jsonl` — what you tried before **and whether it
|
|
23
|
+
worked** (`realizedDelta`). This is the whole point: do not re-propose an
|
|
24
|
+
intervention that already came back negative.
|
|
25
|
+
4. `state/mandate/audit.jsonl` — the decision trail from this morning's pass,
|
|
26
|
+
including every candidate that was rejected and the gate that rejected it.
|
|
27
|
+
5. `state/queues/goals.yaml` — what is already in flight against each objective.
|
|
28
|
+
|
|
29
|
+
### Your job
|
|
30
|
+
|
|
31
|
+
For each objective named in the tick payload:
|
|
32
|
+
|
|
33
|
+
1. State the gap in one line: metric, current value, target, trend, how long it
|
|
34
|
+
has been open.
|
|
35
|
+
2. Propose **at most two** candidate moves. For each one:
|
|
36
|
+
- `title` — a concrete action, not a theme. "Book 6 discovery calls with
|
|
37
|
+
dormant Q2 accounts", not "improve pipeline".
|
|
38
|
+
- `advances` — the objective key. Mandatory. Work that advances nothing is
|
|
39
|
+
work nobody asked for.
|
|
40
|
+
- `expected_delta` — how much of the gap you honestly think this closes, in
|
|
41
|
+
the metric's own units. This is scored against reality next period, so a
|
|
42
|
+
number you do not believe costs you.
|
|
43
|
+
- `success_criteria` — how anyone can tell it worked.
|
|
44
|
+
- `collaborators` — who else is needed, and in what role
|
|
45
|
+
(DOER / CONTRIBUTOR / REVIEWER / APPROVER).
|
|
46
|
+
- `rung` — the lowest execution rung that can actually do it: a single tool
|
|
47
|
+
call, a skill, a plugin/integration, a Claude session, a workflow, or a
|
|
48
|
+
sub-agent team. Never reach for a session when a tool call does it.
|
|
49
|
+
3. If more work is NOT the answer — the gap is flat despite items already in
|
|
50
|
+
flight, or the target was never achievable this period — **say that and stop**.
|
|
51
|
+
Raise the question with 2–3 real options instead of manufacturing tasks.
|
|
52
|
+
Piling tasks onto a target that will not move is the failure mode this whole
|
|
53
|
+
loop exists to prevent.
|
|
54
|
+
|
|
55
|
+
### Hard rules
|
|
56
|
+
|
|
57
|
+
- **Never propose work against an objective you adopted yourself.** You may
|
|
58
|
+
propose an objective and you may report a sample; you may not define, measure
|
|
59
|
+
and be graded on the same number. The loop already excludes these — if you
|
|
60
|
+
spot one, flag it for a human to adopt properly.
|
|
61
|
+
- **A sample sourced `llm` cannot create work.** If the only evidence for a gap
|
|
62
|
+
is your own estimate, the honest move is to fix the sensor — propose a real
|
|
63
|
+
measurement capability — not to create tasks off a guess.
|
|
64
|
+
- **Do not exceed the budget envelope in the tick payload.** If the best move
|
|
65
|
+
costs more than what is left, propose it as an enqueued item with the cost
|
|
66
|
+
stated, so a human can decide.
|
|
67
|
+
- **Anything external, irreversible or financial goes through approval first.**
|
|
68
|
+
Do not execute it and report afterwards.
|
|
69
|
+
|
|
70
|
+
### Output
|
|
71
|
+
|
|
72
|
+
Write your candidates back as queue items in `state/queues/goals.yaml`, in the
|
|
73
|
+
existing item shape — every item carrying `advances`, `expected_delta`,
|
|
74
|
+
`obligation_key`, `disposition` and a populated `why`. An item a human cannot
|
|
75
|
+
trace back to a clause of your charter is an item a human cannot trust.
|
|
76
|
+
|
|
77
|
+
Then post a two-line summary to your principal only if something needs a human:
|
|
78
|
+
a decision, an approval, or a target that should change. Otherwise stay quiet
|
|
79
|
+
and let the work speak.
|