@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,392 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sections/mandate.mjs — the SCHEDULE DERIVATION step. (order 78)
|
|
3
|
+
*
|
|
4
|
+
* Registered by dropping this file into `lib/setup/sections/` — `runner.mjs`
|
|
5
|
+
* globs the directory and sorts by `order`. No dispatcher edit.
|
|
6
|
+
*
|
|
7
|
+
* Order 78 is deliberate: it runs AFTER `org` (75) so `config/org-context.json`
|
|
8
|
+
* and the Cohort-authored `charter.sections` are on disk, and after `inventory`
|
|
9
|
+
* (55) so every capability has been probed. This is the step that finally makes
|
|
10
|
+
* those inert PILLAR sections in `config/agent.json` load-bearing.
|
|
11
|
+
*
|
|
12
|
+
* What it does:
|
|
13
|
+
* detect — is there a compiled plan whose lock matches?
|
|
14
|
+
* prompt — the ONLY place an LLM/human touches the mandate spine. Its output
|
|
15
|
+
* is persisted and human-confirmed, never re-derived at runtime.
|
|
16
|
+
* apply — (1) try `mandate.get` for an ADOPTED server mandate;
|
|
17
|
+
* (2) otherwise derive a PROPOSED tree locally from charter →
|
|
18
|
+
* strategy → KPI categories → priorities, and offer it to hq via
|
|
19
|
+
* `mandate.propose`;
|
|
20
|
+
* (3) cache it; (4) COMPILE obligations deterministically;
|
|
21
|
+
* (5) EMIT cadences.yaml / .cadence-registry.json / .cadence-plists.tsv
|
|
22
|
+
* / plan.yaml; (6) run generate-plists.sh so real launchd jobs
|
|
23
|
+
* exist.
|
|
24
|
+
* verify — plan compiles, hashes match the lock, no obligation cites an
|
|
25
|
+
* unreachable capability, every OUTCOME has a non-LLM sensor, and
|
|
26
|
+
* the mandate cache is fresh.
|
|
27
|
+
*
|
|
28
|
+
* THE SAFETY PROPERTY: nothing is adopted here. A locally-derived objective is
|
|
29
|
+
* `state:'proposed'`, and proposed objectives compile NO obligations — so on a
|
|
30
|
+
* fresh agent this section emits exactly the standard + archetype cadence set
|
|
31
|
+
* that `resolveCadences()` produces today. The schedule only grows once a human
|
|
32
|
+
* adopts an objective in hq. That is what makes this shippable incrementally
|
|
33
|
+
* rather than lockstep, and it is why `generate-cadences.mjs` remains the
|
|
34
|
+
* fallback producer, untouched.
|
|
35
|
+
*
|
|
36
|
+
* Every hook is non-blocking under --headless. apply() is idempotent: identical
|
|
37
|
+
* inputs re-emit byte-identical files, so no launchd plist churns.
|
|
38
|
+
*
|
|
39
|
+
* @module lib/setup/sections/mandate
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
"use strict";
|
|
43
|
+
|
|
44
|
+
import { join } from "node:path";
|
|
45
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
46
|
+
import * as io from "../io.mjs";
|
|
47
|
+
import { readAgentJson } from "../sot.mjs";
|
|
48
|
+
import { runScriptAbs } from "../run-generator.mjs";
|
|
49
|
+
import { readManifest } from "../../capability/inventory.mjs";
|
|
50
|
+
import { deriveMandate, toMandateBody } from "../../mandate/derive.mjs";
|
|
51
|
+
import { readCache, writeCache, stalenessTier, staleSeconds, allObjectives, adoptedObjectives, CACHE_REL } from "../../mandate/cache.mjs";
|
|
52
|
+
import { compilePlan } from "../../plan/compile.mjs";
|
|
53
|
+
import { emitPlan, readLock, PLAN_REL, LOCK_REL } from "../../plan/emit.mjs";
|
|
54
|
+
import { validatePlan } from "../../plan/schema.mjs";
|
|
55
|
+
|
|
56
|
+
const ORG_CONTEXT_REL = join("config", "org-context.json");
|
|
57
|
+
const PRIORITIES_REL = join("config", "priorities.yaml");
|
|
58
|
+
|
|
59
|
+
/** Read a JSON file, tolerating absence/corruption (never throws). */
|
|
60
|
+
function readJson(path) {
|
|
61
|
+
try { return existsSync(path) ? JSON.parse(readFileSync(path, "utf-8")) : null; } catch { return null; }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** config/priorities.yaml → [{id,title}] (best-effort; never throws). */
|
|
65
|
+
export async function readPriorities(agentRoot) {
|
|
66
|
+
const p = join(agentRoot, PRIORITIES_REL);
|
|
67
|
+
if (!existsSync(p)) return [];
|
|
68
|
+
try {
|
|
69
|
+
const m = await import("js-yaml");
|
|
70
|
+
const yaml = m && m.default ? m.default : m;
|
|
71
|
+
const doc = yaml.load(readFileSync(p, "utf-8"));
|
|
72
|
+
const list = Array.isArray(doc) ? doc : (doc && (doc.priorities || doc.items)) || [];
|
|
73
|
+
return (Array.isArray(list) ? list : []).filter(Boolean).map((x) => (typeof x === "string" ? { title: x } : x));
|
|
74
|
+
} catch {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Gather every compiler input from disk. Exported so the standalone
|
|
81
|
+
* `scripts/setup/generate-plan.mjs` and the tests share one loader.
|
|
82
|
+
*
|
|
83
|
+
* @param {object} o - { agentRoot, maestroRoot }
|
|
84
|
+
* @param {object} [deps] - { resolveCadencesImpl, resolveArchetypeImpl, standardCadences }
|
|
85
|
+
* @returns {Promise<object>} { agentJson, archetype, profile, manifest, orgContext, priorities, standardCadences, archetypeCadences, degradations }
|
|
86
|
+
*/
|
|
87
|
+
export async function loadCompilerInputs(o = {}, deps = {}) {
|
|
88
|
+
const { agentRoot, maestroRoot } = o;
|
|
89
|
+
const degradations = [];
|
|
90
|
+
const agentJson = readAgentJson(agentRoot) || {};
|
|
91
|
+
const archetype = agentJson.function && agentJson.altitude ? { function: agentJson.function, altitude: agentJson.altitude } : null;
|
|
92
|
+
if (!archetype) degradations.push({ kind: "no_archetype", reason: "config/agent.json has no { function, altitude } — archetype cadences will not compile" });
|
|
93
|
+
|
|
94
|
+
const manifest = readManifest(agentRoot);
|
|
95
|
+
if (!manifest) degradations.push({ kind: "no_manifest", reason: "config/capability-manifest.json absent — run `maestro setup --only inventory` first; every sensor will fall back to source:human" });
|
|
96
|
+
|
|
97
|
+
const orgContext = readJson(join(agentRoot, ORG_CONTEXT_REL));
|
|
98
|
+
if (!orgContext) degradations.push({ kind: "no_org_context", reason: `${ORG_CONTEXT_REL} absent — the org's strategy streams will not shape the mandate` });
|
|
99
|
+
|
|
100
|
+
const priorities = await readPriorities(agentRoot);
|
|
101
|
+
|
|
102
|
+
let profile = null;
|
|
103
|
+
if (archetype) {
|
|
104
|
+
try {
|
|
105
|
+
const resolve = deps.resolveArchetypeImpl || (await import("../../archetype.mjs")).resolveArchetype;
|
|
106
|
+
profile = await resolve(archetype);
|
|
107
|
+
} catch (e) {
|
|
108
|
+
degradations.push({ kind: "archetype_unresolved", reason: `resolveArchetype failed: ${e && e.message ? e.message : e}` });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let standardCadences = deps.standardCadences;
|
|
113
|
+
let archetypeCadences = [];
|
|
114
|
+
try {
|
|
115
|
+
const cad = await import("../../cadences.mjs");
|
|
116
|
+
if (!standardCadences) standardCadences = cad.STANDARD_CADENCES;
|
|
117
|
+
if (archetype) {
|
|
118
|
+
const resolveCadences = deps.resolveCadencesImpl || cad.resolveCadences;
|
|
119
|
+
archetypeCadences = (await resolveCadences(archetype)).filter((c) => c.scope !== "standard");
|
|
120
|
+
}
|
|
121
|
+
} catch (e) {
|
|
122
|
+
degradations.push({ kind: "cadences_unresolved", reason: `resolveCadences failed: ${e && e.message ? e.message : e}` });
|
|
123
|
+
standardCadences = standardCadences || [];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
void maestroRoot;
|
|
127
|
+
return { agentJson, archetype, profile, manifest, orgContext, priorities, standardCadences: standardCadences || [], archetypeCadences, degradations };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Resolve the org client `call` for this agent, or null when un-enrolled.
|
|
132
|
+
* Never throws; a missing/broken enrollment is logged, never silent.
|
|
133
|
+
*/
|
|
134
|
+
async function resolveCall(agentRoot, deps, log) {
|
|
135
|
+
if (deps.callImpl !== undefined) return deps.callImpl;
|
|
136
|
+
try {
|
|
137
|
+
const client = await import("../../org/client.mjs");
|
|
138
|
+
const cfg = client.loadOrgConfig(agentRoot);
|
|
139
|
+
if (!client.isEnabled(cfg)) {
|
|
140
|
+
log("[mandate] not enrolled in an org — deriving a LOCAL proposed mandate (nothing will be adopted)");
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const cc = client.configFromAgent(cfg);
|
|
144
|
+
return (m, p) => client.call(m, p, cc);
|
|
145
|
+
} catch (e) {
|
|
146
|
+
log(`[mandate] org client unavailable (${e && e.message ? e.message : e}) — deriving a LOCAL proposed mandate`);
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Resolve the mandate: the ADOPTED server snapshot if hq has one, otherwise a
|
|
153
|
+
* locally-derived PROPOSED tree.
|
|
154
|
+
*
|
|
155
|
+
* The server→local resolution itself is NOT reimplemented here — it is
|
|
156
|
+
* `lib/mandate/refresh.refreshMandate`, which already owns the fetch, the
|
|
157
|
+
* checksum check, the "a local derivation never supersedes an adopted snapshot"
|
|
158
|
+
* rule, and the vendored-protocol-gap message. This function adds the one thing
|
|
159
|
+
* the refresher deliberately does not do: OFFERING the derived tree to hq via
|
|
160
|
+
* `mandate.propose`.
|
|
161
|
+
*
|
|
162
|
+
* @param {object} o - { agentRoot, inputs, memberId?, overrides? }
|
|
163
|
+
* @param {object} [deps] - { callImpl, now, logImpl }
|
|
164
|
+
* @returns {Promise<{record:object, derived:object|null, source:string, degraded:boolean, reason:string}>}
|
|
165
|
+
*/
|
|
166
|
+
export async function resolveMandate(o = {}, deps = {}) {
|
|
167
|
+
const { agentRoot, inputs } = o;
|
|
168
|
+
const log = deps.logImpl || ((m) => io.note(m, { dim: true }));
|
|
169
|
+
const call = await resolveCall(agentRoot, deps, log);
|
|
170
|
+
|
|
171
|
+
const { refreshMandate } = await import("../../mandate/refresh.mjs");
|
|
172
|
+
const res = await refreshMandate(agentRoot, {
|
|
173
|
+
now: deps.now ? () => deps.now() : undefined,
|
|
174
|
+
log: (level, m) => log(m),
|
|
175
|
+
memberId: o.memberId || null,
|
|
176
|
+
callImpl: typeof call === "function" ? call : undefined,
|
|
177
|
+
offline: typeof call !== "function",
|
|
178
|
+
derivation: {
|
|
179
|
+
charterSections: (inputs.agentJson && inputs.agentJson.charter && Array.isArray(inputs.agentJson.charter.sections))
|
|
180
|
+
? inputs.agentJson.charter.sections
|
|
181
|
+
: [],
|
|
182
|
+
strategy: inputs.orgContext && inputs.orgContext.strategy,
|
|
183
|
+
kpiCategories: (inputs.profile && inputs.profile.kpiCategories) || [],
|
|
184
|
+
priorities: inputs.priorities,
|
|
185
|
+
manifest: inputs.manifest,
|
|
186
|
+
overrides: o.overrides || {},
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const derived = res.source === "server" ? null : { counts: res.counts || {}, degradations: res.degradations || [] };
|
|
191
|
+
|
|
192
|
+
// OFFER the proposals to hq. Fail-open + logged: an agent may PROPOSE, only a
|
|
193
|
+
// human/manager may ADOPT (`mandate.adopt` refuses actor === owner). We never
|
|
194
|
+
// call adopt, from anywhere, for any reason.
|
|
195
|
+
const proposals = res.source === "local" ? allObjectives(res.record) : [];
|
|
196
|
+
if (typeof call === "function" && proposals.length) {
|
|
197
|
+
let posted = 0;
|
|
198
|
+
for (const obj of proposals) {
|
|
199
|
+
try {
|
|
200
|
+
const r = await call("mandate.propose", {
|
|
201
|
+
key: obj.key, kind: obj.kind, text: obj.text, metric: obj.metric, unit: obj.unit,
|
|
202
|
+
direction: obj.direction, target: obj.target, tolerance: obj.tolerance,
|
|
203
|
+
cadence: obj.cadence, sensor: obj.sensor, parentKey: obj.parentKey,
|
|
204
|
+
});
|
|
205
|
+
if (r && r.ok) posted++;
|
|
206
|
+
} catch { /* a per-objective failure must not abort the rest */ }
|
|
207
|
+
}
|
|
208
|
+
if (posted !== proposals.length) {
|
|
209
|
+
log(`[mandate] proposed ${posted}/${proposals.length} objectives to hq — the rest stayed local (the plan compiles from the cache either way)`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return { record: res.record, derived, source: res.source, degraded: !!res.degraded, reason: res.reason };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Compile + emit the plan from on-disk inputs. Shared by apply() and
|
|
218
|
+
* `scripts/setup/generate-plan.mjs`.
|
|
219
|
+
* @returns {Promise<{ok:boolean, plan:object, emit:object, inputs:object, record:object}>}
|
|
220
|
+
*/
|
|
221
|
+
export async function compileAndEmit(o = {}, deps = {}) {
|
|
222
|
+
const { agentRoot, maestroRoot } = o;
|
|
223
|
+
const inputs = o.inputs || (await loadCompilerInputs({ agentRoot, maestroRoot }, deps));
|
|
224
|
+
const record = o.record || readCache(agentRoot, deps) || { body: {}, version: 0 };
|
|
225
|
+
|
|
226
|
+
const plan = compilePlan({
|
|
227
|
+
mandate: record.body || {},
|
|
228
|
+
mandateVersion: record.version || 0,
|
|
229
|
+
manifest: inputs.manifest || { entries: [] },
|
|
230
|
+
profile: inputs.profile,
|
|
231
|
+
orgContext: inputs.orgContext,
|
|
232
|
+
standardCadences: inputs.standardCadences,
|
|
233
|
+
archetypeCadences: inputs.archetypeCadences,
|
|
234
|
+
calendarEvents: o.calendarEvents || [],
|
|
235
|
+
concurrency: o.concurrency,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const emit = await emitPlan({
|
|
239
|
+
agentRoot,
|
|
240
|
+
plan,
|
|
241
|
+
manifest: inputs.manifest || { entries: [] },
|
|
242
|
+
archetype: inputs.archetype ? `${inputs.archetype.function}@${inputs.archetype.altitude}` : null,
|
|
243
|
+
enforcement: o.enforcement || "observe-only",
|
|
244
|
+
at: (typeof deps.now === "function" ? deps.now() : new Date().toISOString()),
|
|
245
|
+
}, deps);
|
|
246
|
+
|
|
247
|
+
return { ok: emit.ok, plan, emit, inputs, record };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export default {
|
|
251
|
+
id: "mandate",
|
|
252
|
+
label: "Mandate & derived schedule (obligations → launchd jobs)",
|
|
253
|
+
order: 78,
|
|
254
|
+
|
|
255
|
+
async detect(ctx) {
|
|
256
|
+
const planPath = join(ctx.agentRoot, PLAN_REL);
|
|
257
|
+
const lock = readLock(ctx.agentRoot);
|
|
258
|
+
const cache = readCache(ctx.agentRoot);
|
|
259
|
+
if (existsSync(planPath) && lock && lock.obligationsHash) {
|
|
260
|
+
const tier = stalenessTier(cache, Date.now());
|
|
261
|
+
return { status: "complete", summary: `plan ${lock.obligationsHash.slice(0, 12)} · mandate v${lock.mandateVersion ?? 0} (${tier})` };
|
|
262
|
+
}
|
|
263
|
+
const agentJson = readAgentJson(ctx.agentRoot) || {};
|
|
264
|
+
const hasCharter = !!(agentJson.charter && Array.isArray(agentJson.charter.sections) && agentJson.charter.sections.length);
|
|
265
|
+
if (hasCharter || cache) return { status: "partial", summary: "charter present, no compiled plan", missing: [PLAN_REL, LOCK_REL] };
|
|
266
|
+
return { status: "unconfigured", missing: [PLAN_REL, CACHE_REL] };
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
async prompt(ctx) {
|
|
270
|
+
// This is the ONE place prose becomes a structured mandate. It is
|
|
271
|
+
// interactive, its output is persisted, and it is never re-derived at
|
|
272
|
+
// runtime (that is what keeps obligationsHash deterministic).
|
|
273
|
+
const propose = await io.confirm(ctx, {
|
|
274
|
+
message: "Derive a proposed objective tree from this agent's charter, the org strategy and its KPI categories? (nothing is adopted — a human adopts in Cohort)",
|
|
275
|
+
key: "mandatePropose",
|
|
276
|
+
default: true,
|
|
277
|
+
});
|
|
278
|
+
ctx.answers.mandatePropose = propose;
|
|
279
|
+
if (!propose || io.isHeadless(ctx)) return;
|
|
280
|
+
|
|
281
|
+
const enforcement = await io.select(ctx, {
|
|
282
|
+
message: "Plan enforcement mode for the first 14 days",
|
|
283
|
+
key: "planEnforcement",
|
|
284
|
+
choices: [
|
|
285
|
+
{ value: "observe-only", label: "observe-only", hint: "record drift, create no tasks (recommended)" },
|
|
286
|
+
{ value: "active", label: "active", hint: "the plan may create work immediately" },
|
|
287
|
+
],
|
|
288
|
+
default: "observe-only",
|
|
289
|
+
});
|
|
290
|
+
ctx.answers.planEnforcement = enforcement;
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
async apply(ctx) {
|
|
294
|
+
const deps = (ctx.mandateDeps && typeof ctx.mandateDeps === "object") ? ctx.mandateDeps : {};
|
|
295
|
+
const log = deps.logImpl || ((m) => io.note(m, { dim: true }));
|
|
296
|
+
|
|
297
|
+
const inputs = await loadCompilerInputs({ agentRoot: ctx.agentRoot, maestroRoot: ctx.maestroRoot }, deps);
|
|
298
|
+
for (const d of inputs.degradations) log(`[mandate] ${d.kind}: ${d.reason}`);
|
|
299
|
+
|
|
300
|
+
const propose = ctx.answers && typeof ctx.answers.mandatePropose === "boolean" ? ctx.answers.mandatePropose : true;
|
|
301
|
+
let record = readCache(ctx.agentRoot, deps);
|
|
302
|
+
if (propose || !record) {
|
|
303
|
+
const res = await resolveMandate({ agentRoot: ctx.agentRoot, inputs, memberId: (inputs.agentJson.orgProfile && inputs.agentJson.orgProfile.memberId) || null, overrides: (ctx.answers && ctx.answers.mandateOverrides) || {} }, deps);
|
|
304
|
+
record = res.record;
|
|
305
|
+
if (res.derived) {
|
|
306
|
+
const c = res.derived.counts;
|
|
307
|
+
io.note(`[mandate] derived ${c.proposed} PROPOSED objectives (${c.pillars} pillar · ${c.objectives} objective · ${c.goals} goal; ${c.withMethodSensor} with a method sensor). Adopt them in Cohort to activate them.`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const { ok, plan, emit } = await compileAndEmit({
|
|
312
|
+
agentRoot: ctx.agentRoot, maestroRoot: ctx.maestroRoot, inputs, record,
|
|
313
|
+
enforcement: (ctx.answers && ctx.answers.planEnforcement) || "observe-only",
|
|
314
|
+
calendarEvents: (ctx.answers && ctx.answers.calendarBusy) || [],
|
|
315
|
+
}, deps);
|
|
316
|
+
|
|
317
|
+
if (!ok) {
|
|
318
|
+
// FAIL-CLOSED and fatal: a plan that does not validate must never be
|
|
319
|
+
// half-written, because a missing launchd job fails silently forever.
|
|
320
|
+
throw new Error(`mandate: plan failed validation, nothing written:\n - ${emit.errors.slice(0, 10).join("\n - ")}`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
io.note(`[mandate] compiled ${plan.counts.total} obligations (${plan.counts.SCHEDULE} SCHEDULE · ${plan.counts.OUTCOME} OUTCOME · ${plan.counts.REACT} REACT) from ${plan.counts.adoptedObjectives} adopted / ${plan.counts.proposedObjectives} proposed objectives`);
|
|
324
|
+
for (const d of plan.drift) log(`[mandate] drift ${d.kind}${d.key ? ` (${d.key})` : ""}: ${JSON.stringify(d.detail)}`);
|
|
325
|
+
|
|
326
|
+
// launchd plists — generate-plists.sh reads config/.cadence-plists.tsv
|
|
327
|
+
// verbatim, so the derived jobs become REAL launchd jobs with no shell edit.
|
|
328
|
+
if (deps.skipPlists !== true) {
|
|
329
|
+
const plistGen = join(ctx.agentRoot, "scripts", "local-triggers", "generate-plists.sh");
|
|
330
|
+
if (existsSync(plistGen)) {
|
|
331
|
+
const pr = runScriptAbs(plistGen, ctx.agentRoot, { bash: true, maestroRoot: ctx.maestroRoot });
|
|
332
|
+
if (!pr.ok) log(`[mandate] generate-plists.sh did not complete: ${(pr.stderr || "").trim().slice(0, 300)}`);
|
|
333
|
+
} else {
|
|
334
|
+
log(`[mandate] no scripts/local-triggers/generate-plists.sh in this repo — TSV written, launchd jobs not emitted`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
async verify(ctx) {
|
|
340
|
+
const checks = [];
|
|
341
|
+
const planPath = join(ctx.agentRoot, PLAN_REL);
|
|
342
|
+
const lock = readLock(ctx.agentRoot);
|
|
343
|
+
const manifest = readManifest(ctx.agentRoot);
|
|
344
|
+
const cache = readCache(ctx.agentRoot);
|
|
345
|
+
|
|
346
|
+
checks.push({ name: `${PLAN_REL} present`, ok: existsSync(planPath), remedy: existsSync(planPath) ? undefined : "run `maestro setup --only mandate`" });
|
|
347
|
+
checks.push({ name: `${LOCK_REL} present`, ok: !!lock, remedy: lock ? undefined : "run `maestro setup --only mandate`" });
|
|
348
|
+
if (!lock || !existsSync(planPath)) return { ok: false, checks };
|
|
349
|
+
|
|
350
|
+
// Recompile from the same inputs and compare hashes — this is what catches
|
|
351
|
+
// "someone hand-edited config/cadences.yaml" and "the manifest moved".
|
|
352
|
+
// The SAME deps apply() used, or the recompile is comparing two different
|
|
353
|
+
// worlds and the check is worthless.
|
|
354
|
+
const deps = (ctx.mandateDeps && typeof ctx.mandateDeps === "object") ? ctx.mandateDeps : {};
|
|
355
|
+
const inputs = await loadCompilerInputs({ agentRoot: ctx.agentRoot, maestroRoot: ctx.maestroRoot }, deps);
|
|
356
|
+
const plan = compilePlan({
|
|
357
|
+
mandate: (cache && cache.body) || {},
|
|
358
|
+
mandateVersion: (cache && cache.version) || 0,
|
|
359
|
+
manifest: manifest || { entries: [] },
|
|
360
|
+
profile: inputs.profile,
|
|
361
|
+
orgContext: inputs.orgContext,
|
|
362
|
+
standardCadences: inputs.standardCadences,
|
|
363
|
+
archetypeCadences: inputs.archetypeCadences,
|
|
364
|
+
});
|
|
365
|
+
const hashOk = plan.obligationsHash === lock.obligationsHash;
|
|
366
|
+
checks.push({ name: "obligationsHash matches compile.lock", ok: hashOk, remedy: hashOk ? undefined : "inputs changed since the last compile — re-run `maestro setup --only mandate`" });
|
|
367
|
+
|
|
368
|
+
const v = validatePlan(plan, { manifest: manifest || { entries: [] } });
|
|
369
|
+
checks.push({ name: "plan validates (no obligation cites an unreachable capability)", ok: v.ok, remedy: v.ok ? undefined : v.errors.slice(0, 3).join("; ") });
|
|
370
|
+
|
|
371
|
+
const llmSensors = plan.obligations.filter((ob) => ob.kind === "OUTCOME" && ob.sensor && ob.sensor.source === "llm").length;
|
|
372
|
+
checks.push({ name: "zero OUTCOME obligations with an llm sensor", ok: llmSensors === 0, remedy: llmSensors === 0 ? undefined : `${llmSensors} OUTCOME obligation(s) are LLM-graded — an llm sample may never close a loop` });
|
|
373
|
+
|
|
374
|
+
const secs = staleSeconds(cache, Date.now());
|
|
375
|
+
const fresh = secs < 24 * 3600;
|
|
376
|
+
checks.push({
|
|
377
|
+
name: `mandate cache freshness (${Number.isFinite(secs) ? `${Math.round(secs / 3600)}h` : "never fetched"})`,
|
|
378
|
+
ok: true,
|
|
379
|
+
remedy: fresh ? undefined : `cache is ${stalenessTier(cache, Date.now())} — self-directed work is suspended until it refreshes`,
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
const adopted = adoptedObjectives(cache).length;
|
|
383
|
+
const all = allObjectives(cache).length;
|
|
384
|
+
checks.push({
|
|
385
|
+
name: `objectives: ${adopted} adopted / ${all} total`,
|
|
386
|
+
ok: true,
|
|
387
|
+
remedy: adopted === 0 && all > 0 ? "no objective is adopted yet — the agent runs standard + archetype cadences only until a human adopts in Cohort" : undefined,
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
return { ok: checks.every((c) => c.ok), checks };
|
|
391
|
+
},
|
|
392
|
+
};
|