pantheon-opencode 1.3.4 → 1.3.5-beta.57.2e03676

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.
@@ -1,6 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * opencode.mjs — OpenCode platform installer
4
+ *
5
+ * Dual-version install (Phase 3): the generated config is V1-shaped and valid
6
+ * under BOTH OpenCode V1 (`opencode` 1.18.x) and V2 (`opencode2`
7
+ * v0.0.0-next-17444). V2 reads the same config locations and normalizes V1
8
+ * fields in memory — do NOT convert to native V2 format. Known V2 beta gaps
9
+ * handled here: top-level `subagent_depth` is silently ignored (migrated to
10
+ * `experimental.subagent_depth`), and the `instructions` config key is
11
+ * accepted-but-not-loaded (content consolidated into AGENTS.md, which both
12
+ * versions load). Pass --version v2 to pantheon-init for an informational
13
+ * label; state isolation is handled at runtime via OPENCODE_DB.
4
14
  */
5
15
 
6
16
  import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from 'node:fs'
@@ -221,6 +231,20 @@ export async function installOpenCode(
221
231
  const componentSet = new Set(components)
222
232
  const stats = summary.opencode
223
233
 
234
+ // V1/V2 dual-version awareness (Phase 3): the SAME config works under both
235
+ // OpenCode V1 (`opencode`) and V2 (`opencode2`, beta v0.0.0-next-17444).
236
+ // V2 normalizes V1 fields in memory (no rewrite) and isolates its state DB
237
+ // via OPENCODE_DB (~/.local/share/opencode/opencode-v2.db) with a distinct
238
+ // service port (49375 vs V1's 49374). The installer writes one shared
239
+ // config; the --version flag only labels the install for the operator.
240
+ const version = opts.version === 'v2' ? 'v2' : 'v1'
241
+ if (version === 'v2') {
242
+ info(
243
+ 'OpenCode V2 target — shared config; state isolated via OPENCODE_DB ' +
244
+ '(~/.local/share/opencode/opencode-v2.db), service port 49375',
245
+ )
246
+ }
247
+
224
248
  // Interactive mode handling
225
249
  const stdinTTY = process.stdin.isTTY && process.stdout.isTTY
226
250
  const forceInteractive = opts.interactive === true
@@ -333,8 +357,13 @@ export async function installOpenCode(
333
357
  }
334
358
 
335
359
  // -----------------------------------------------------------------------
336
- // 2.5 Install instructions: AGENTS.md + instructions/ (--components instructions)
360
+ // 2.5 Install instructions: AGENTS.md only (--components instructions)
337
361
  // -----------------------------------------------------------------------
362
+ // AGENTS.md is the single instruction file both V1 and V2 load; the
363
+ // generated file embeds every src/instructions/*.instructions.md body, so
364
+ // the individual files are NOT copied anymore (nothing references
365
+ // <config>/instructions/ after the section-D merge fix — copying them would
366
+ // just leave stale duplicates on disk).
338
367
  if (componentSet.has('instructions')) {
339
368
  section('\uD83D\uDCCB Instructions')
340
369
  // AGENTS.md
@@ -346,14 +375,6 @@ export async function installOpenCode(
346
375
  if (status === 'created') stats.created++
347
376
  else stats.skipped++
348
377
  }
349
- // instructions/ directory
350
- const srcInstr = join(ROOT, 'src', 'instructions')
351
- const dstInstr = join(target, 'instructions')
352
- if (existsSync(srcInstr)) {
353
- const instrResult = syncDir(srcInstr, dstInstr, dryRun, clean)
354
- stats.created += instrResult.created
355
- stats.skipped += instrResult.skipped
356
- }
357
378
  }
358
379
 
359
380
  // -----------------------------------------------------------------------
@@ -661,11 +682,36 @@ export async function installOpenCode(
661
682
  if (!config.default_agent && pantheonConfig.default_agent) {
662
683
  config.default_agent = pantheonConfig.default_agent
663
684
  }
685
+ // Merge top-level model/small_model from the repo config when the target
686
+ // config has no top-level `model` (fresh sandbox installs have none). The
687
+ // repo's opencode.json carries the default pair
688
+ // (opencode-go/deepseek-v4-flash); a user-set model is never overwritten.
689
+ if (!config.model && pantheonConfig.model) {
690
+ config.model = pantheonConfig.model
691
+ if (!config.small_model && pantheonConfig.small_model) {
692
+ config.small_model = pantheonConfig.small_model
693
+ }
694
+ }
664
695
  // Merge subagent_depth HERE (the config merge always runs, including on a
665
696
  // fresh install) instead of a file-existence-gated block, so run #1 already
666
697
  // produces the final config — run #2 must be a byte-identical no-op (#19).
667
- if (config.subagent_depth === undefined) {
668
- config.subagent_depth = 2
698
+ //
699
+ // V1/V2 compat (Phase 3): top-level `subagent_depth` is in V2's
700
+ // unsupportedTopLevel list and is SILENTLY IGNORED. The accepted form is
701
+ // `experimental.subagent_depth`. Migrate any existing top-level value into
702
+ // experimental (preserving the user's choice) and default fresh installs to
703
+ // experimental.subagent_depth = 2. V1 reads experimental.subagent_depth
704
+ // too, so the V1-shape config stays valid under both versions.
705
+ if (config.experimental === undefined || typeof config.experimental !== 'object') {
706
+ config.experimental = {}
707
+ }
708
+ if (config.subagent_depth !== undefined) {
709
+ if (config.experimental.subagent_depth === undefined) {
710
+ config.experimental.subagent_depth = config.subagent_depth
711
+ }
712
+ delete config.subagent_depth
713
+ } else if (config.experimental.subagent_depth === undefined) {
714
+ config.experimental.subagent_depth = 2
669
715
  }
670
716
 
671
717
  // --------------------------------------------------------------------
@@ -695,16 +741,24 @@ export async function installOpenCode(
695
741
  }
696
742
  }
697
743
 
698
- // The runtime hooks plugin MUST always be registered. The packaged
699
- // opencode.json no longer carries dev-machine plugin paths (removed in
700
- // 3e552cc), so config.plugin would otherwise stay empty and the
701
- // pantheon-hooks runtime would never load. Register it unconditionally,
702
- // replacing any stale entry for the same file (same basename-dedup as
703
- // above) so installs and upgrades are idempotent and hermetic.
704
- const hooksPlugin = resolveInstalledPlugin('src/plugins/pantheon-hooks.ts')
705
- const hooksFile = basename(hooksPlugin)
706
- config.plugin = config.plugin.filter((p) => basename(p) !== hooksFile)
707
- config.plugin.push(hooksPlugin)
744
+ // The two Pantheon plugins MUST always be registered, on fresh installs AND
745
+ // upgrades. The packaged opencode.json carries no `plugin` key (dev-machine
746
+ // paths removed in 3e552cc), so config.plugin would otherwise only contain
747
+ // whatever the user had — a fresh install would register NOTHING and lose
748
+ // delegation tools, GoalLoop, cost command, hashline and vision. Register
749
+ // both unconditionally, replacing any stale entry for the same file
750
+ // (basename-dedup) so installs and upgrades are idempotent and hermetic
751
+ // while preserving any user plugins.
752
+ const ensurePantheonPlugin = (ref) => {
753
+ const resolved = resolveInstalledPlugin(ref)
754
+ const file = basename(resolved)
755
+ config.plugin = config.plugin.filter((p) => basename(p) !== file)
756
+ config.plugin.push(resolved)
757
+ }
758
+ // Root-level delegation plugin (PR #45): tools, GoalLoop, cost, hashline.
759
+ ensurePantheonPlugin('src/plugin.ts')
760
+ // Runtime hooks plugin: chat hooks, hook-runner, TUI wiring.
761
+ ensurePantheonPlugin('src/plugins/pantheon-hooks.ts')
708
762
 
709
763
  if (!config.provider && pantheonConfig.provider) {
710
764
  config.provider = deepClone(pantheonConfig.provider)
@@ -747,10 +801,25 @@ export async function installOpenCode(
747
801
  // --------------------------------------------------------------------
748
802
  // D. Merge instructions paths
749
803
  // --------------------------------------------------------------------
750
- const pantheonInstructions = ['AGENTS.md', 'instructions/*.instructions.md']
804
+ // AGENTS.md is the single instruction file both V1 and V2 load (V1
805
+ // auto-discovers it, V2 loads it explicitly). The generated AGENTS.md
806
+ // (scripts/build-agents-md.mjs) already embeds every
807
+ // src/instructions/*.instructions.md body, so the `instructions/*.md`
808
+ // glob is no longer needed — keeping it would duplicate content under V1
809
+ // and is ignored under V2 anyway. Only ensure AGENTS.md is present.
810
+ const pantheonInstructions = ['AGENTS.md']
751
811
  if (!config.instructions) {
752
812
  config.instructions = [...pantheonInstructions]
753
813
  } else {
814
+ // Strip stale per-file instruction globs from PREVIOUS installs
815
+ // (e.g. `instructions/*.instructions.md` / `src/instructions/*.md`).
816
+ // The merge is additive-only otherwise, so without this filter existing
817
+ // configs would keep loading instruction content twice under V1
818
+ // (generated AGENTS.md + individual files). User entries that are not
819
+ // *.instructions.md globs are preserved.
820
+ config.instructions = config.instructions.filter(
821
+ (instr) => typeof instr !== 'string' || !instr.endsWith('.instructions.md'),
822
+ )
754
823
  for (const instr of pantheonInstructions) {
755
824
  if (!config.instructions.includes(instr)) {
756
825
  config.instructions.push(instr)
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+ /** Compute and optionally apply the beta version for a release PR. */
3
+
4
+ import { readFileSync, writeFileSync } from 'node:fs'
5
+ import { join } from 'node:path'
6
+
7
+ export const MANIFESTS = [
8
+ { file: 'package.json', kind: 'json' },
9
+ { file: 'pyproject.toml', kind: 'toml' },
10
+ { file: 'plugin.json', kind: 'json' },
11
+ { file: 'src/plugins/tui/package.json', kind: 'json' },
12
+ ]
13
+
14
+ const STABLE = /^(\d+)\.(\d+)\.(\d+)$/
15
+
16
+ export function nextStableVersion(latest, intent = 'patch') {
17
+ const match = STABLE.exec(String(latest))
18
+ if (!match) throw new Error(`npm latest is not a stable semver: "${latest}"`)
19
+ const [major, minor, patch] = match.slice(1).map(Number)
20
+ if (!['patch', 'minor', 'major'].includes(intent)) {
21
+ throw new Error(`release intent must be patch, minor, or major: "${intent}"`)
22
+ }
23
+ if (intent === 'major') return `${major + 1}.0.0`
24
+ if (intent === 'minor') return `${major}.${minor + 1}.0`
25
+ return `${major}.${minor}.${patch + 1}`
26
+ }
27
+
28
+ export function betaVersion(latest, intent, pr, sha) {
29
+ if (!/^\d+$/.test(String(pr)) || Number(pr) < 1) throw new Error(`invalid PR number: "${pr}"`)
30
+ const shortSha = String(sha).slice(0, 7)
31
+ if (!/^[0-9a-f]{7,}$/i.test(shortSha)) throw new Error(`invalid commit SHA: "${sha}"`)
32
+ return `${nextStableVersion(latest, intent)}-beta.${pr}.${shortSha}`
33
+ }
34
+
35
+ function writeVersion(root, { file, kind }, version) {
36
+ const path = join(root, file)
37
+ const raw = readFileSync(path, 'utf8')
38
+ if (kind === 'toml') {
39
+ const updated = raw.replace(/^(version\s*=\s*")[^"]+("\s*)$/m, `$1${version}$2`)
40
+ if (updated === raw) throw new Error(`no version field in ${file}`)
41
+ writeFileSync(path, updated)
42
+ return
43
+ }
44
+ const content = JSON.parse(raw)
45
+ content.version = version
46
+ writeFileSync(path, `${JSON.stringify(content, null, 2)}\n`)
47
+ }
48
+
49
+ export function applyBetaVersion(root, version) {
50
+ for (const manifest of MANIFESTS) writeVersion(root, manifest, version)
51
+ // Keep npm's lockfile metadata consistent without changing dependency data.
52
+ const lockPath = join(root, 'package-lock.json')
53
+ const lock = JSON.parse(readFileSync(lockPath, 'utf8'))
54
+ lock.version = version
55
+ if (lock.packages?.['']) lock.packages[''].version = version
56
+ writeFileSync(lockPath, `${JSON.stringify(lock, null, 2)}\n`)
57
+ }
58
+
59
+ const args = new Map(
60
+ process.argv.slice(2).map((arg) => {
61
+ const [key, ...value] = arg.split('=')
62
+ return [key.replace(/^--/, ''), value.join('=')]
63
+ }),
64
+ )
65
+
66
+ if (args.has('latest')) {
67
+ try {
68
+ const version = betaVersion(
69
+ args.get('latest'),
70
+ args.get('intent') || 'patch',
71
+ args.get('pr'),
72
+ args.get('sha'),
73
+ )
74
+ if (args.has('apply')) applyBetaVersion(process.cwd(), version)
75
+ console.log(version)
76
+ } catch (error) {
77
+ console.error(`release-beta-version: ${error.message}`)
78
+ process.exit(1)
79
+ }
80
+ }
@@ -89,7 +89,9 @@ for (const dir of [srcSkillsDir, dotSkillsDir]) {
89
89
  if (existsSync(join(skillDir, 'SKILL.md')) && !existingSkills.includes(d)) {
90
90
  existingSkills.push(d)
91
91
  }
92
- } catch { /* skip */ }
92
+ } catch {
93
+ /* skip */
94
+ }
93
95
  }
94
96
  }
95
97
  }
@@ -165,6 +167,168 @@ for (const w of presetValidation.warnings) {
165
167
  }
166
168
  console.log(` Presets defined: ${Object.keys(presetDefs).length}`)
167
169
 
170
+ // G. Delegation Matrix Invariants (B1 — Zeus strict + read-only exceptions)
171
+ console.log(`\n Delegation matrix invariants:`)
172
+
173
+ // G1. can_delegate: true only for zeus, athena (→apollo), hermes (→apollo)
174
+ const CAN_DELEGATE_AGENTS = ['zeus', 'athena', 'hermes']
175
+ for (const [name, agentInfo] of Object.entries(routing.agents || {})) {
176
+ if (agentInfo.can_delegate === true) {
177
+ check(
178
+ CAN_DELEGATE_AGENTS.includes(name),
179
+ `Agent "${name}" has can_delegate: true but is not in allowed list [${CAN_DELEGATE_AGENTS}]`,
180
+ )
181
+ }
182
+ }
183
+
184
+ // G3. subagent_can_delegate_to only references existing agents
185
+ for (const [name, agentInfo] of Object.entries(routing.agents || {})) {
186
+ const delegates = agentInfo.subagent_can_delegate_to || []
187
+ for (const target of delegates) {
188
+ check(
189
+ routingAgents.includes(target),
190
+ `Agent "${name}" can_delegate_to unknown agent "${target}"`,
191
+ )
192
+ }
193
+ }
194
+
195
+ // G4. athena and hermes can only delegate to apollo (read-only exception)
196
+ for (const agentName of ['athena', 'hermes']) {
197
+ const agentInfo = routing.agents[agentName]
198
+ if (agentInfo) {
199
+ const delegates = agentInfo.subagent_can_delegate_to || []
200
+ check(
201
+ delegates.length <= 1 && delegates[0] === 'apollo',
202
+ `Agent "${agentName}" subagent_can_delegate_to must be [apollo] only, got [${delegates}]`,
203
+ )
204
+ }
205
+ }
206
+
207
+ // G5. Frontmatter permission.task invariants
208
+ const AGENT_FILES_DIR = join(ROOT, 'src', 'agents')
209
+ const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---/
210
+
211
+ function parseFrontmatter(filePath) {
212
+ const content = readFileSync(filePath, 'utf8')
213
+ const match = content.match(FRONTMATTER_RE)
214
+ if (!match) return null
215
+ try {
216
+ return yaml.load(match[1])
217
+ } catch {
218
+ return null
219
+ }
220
+ }
221
+
222
+ // G5a. Frontmatter mode must match routing.yml task eligibility.
223
+ const frontmatterByAgent = new Map()
224
+ for (const agentName of canonicalFiles) {
225
+ frontmatterByAgent.set(agentName, parseFrontmatter(join(AGENT_FILES_DIR, `${agentName}.md`)))
226
+ }
227
+
228
+ for (const [name, agentInfo] of Object.entries(routing.agents || {})) {
229
+ const fm = frontmatterByAgent.get(name)
230
+ if (!fm) continue
231
+
232
+ if (name === 'zeus') {
233
+ check(fm.mode === 'primary', 'Agent "zeus" must use mode primary')
234
+ } else if (agentInfo.can_receive_delegation === true) {
235
+ const expectedMode = agentInfo.user_invocable === true ? 'all' : 'subagent'
236
+ check(
237
+ fm.mode === expectedMode,
238
+ `Agent "${name}" mode must be "${expectedMode}" for routing.yml eligibility, got "${fm.mode}"`,
239
+ )
240
+ }
241
+ }
242
+
243
+ // G5b. Zeus targets must exist, receive delegation, and never be primary.
244
+ for (const targetEntry of routing.delegation?.zeus || []) {
245
+ const target = typeof targetEntry === 'string' ? targetEntry : targetEntry.target
246
+ const targetInfo = routing.agents[target]
247
+ const targetFm = frontmatterByAgent.get(target)
248
+ check(targetInfo !== undefined, `Zeus delegation target "${target}" must exist`)
249
+ if (targetInfo && targetFm) {
250
+ check(
251
+ targetInfo.can_receive_delegation === true && targetFm.mode !== 'primary',
252
+ `Zeus delegation target "${target}" must not be mode primary and must receive delegation`,
253
+ )
254
+ }
255
+ }
256
+
257
+ // G5c. Every subagent delegation target must exist and be eligible.
258
+ for (const [name, agentInfo] of Object.entries(routing.agents || {})) {
259
+ for (const target of agentInfo.subagent_can_delegate_to || []) {
260
+ const targetInfo = routing.agents[target]
261
+ const targetFm = frontmatterByAgent.get(target)
262
+ check(targetInfo !== undefined, `Agent "${name}" can_delegate_to unknown agent "${target}"`)
263
+ if (targetInfo && targetFm) {
264
+ check(
265
+ targetInfo.can_receive_delegation === true && targetFm.mode !== 'primary',
266
+ `Agent "${name}" can_delegate_to target "${target}" is not eligible`,
267
+ )
268
+ }
269
+ }
270
+ }
271
+
272
+ // Expected permission.task matrix
273
+ const EXPECTED_TASK_PERMISSIONS = {
274
+ zeus: { '*': 'allow' },
275
+ athena: { '*': 'deny', apollo: 'allow' },
276
+ hermes: { '*': 'deny', apollo: 'allow' },
277
+ themis: { '*': 'deny' },
278
+ aphrodite: { '*': 'deny' },
279
+ demeter: { '*': 'deny' },
280
+ apollo: { '*': 'deny' },
281
+ gaia: { '*': 'deny' },
282
+ prometheus: { '*': 'deny' },
283
+ hephaestus: { '*': 'deny' },
284
+ nyx: { '*': 'deny' },
285
+ iris: { '*': 'deny' },
286
+ talos: { '*': 'deny' },
287
+ mnemosyne: { '*': 'deny' },
288
+ }
289
+
290
+ for (const agentName of canonicalFiles) {
291
+ const fmPath = join(AGENT_FILES_DIR, `${agentName}.md`)
292
+ const fm = parseFrontmatter(fmPath)
293
+ if (!fm) {
294
+ check(false, `Agent "${agentName}" has no valid frontmatter`)
295
+ continue
296
+ }
297
+
298
+ // G5a. permission.task must exist
299
+ const hasTask = fm.permission?.task
300
+ check(hasTask, `Agent "${agentName}" missing permission.task in frontmatter`)
301
+
302
+ if (!hasTask) continue
303
+
304
+ // G5b. Only zeus may have "*": allow
305
+ const starRule = fm.permission.task['*']
306
+ if (starRule === 'allow') {
307
+ check(
308
+ agentName === 'zeus',
309
+ `Agent "${agentName}" has permission.task "*": allow — only zeus is allowed`,
310
+ )
311
+ }
312
+
313
+ // G5c. Verify exact expected permissions
314
+ const expected = EXPECTED_TASK_PERMISSIONS[agentName]
315
+ if (expected) {
316
+ const actual = fm.permission.task
317
+ const actualKeys = Object.keys(actual).sort()
318
+ const expectedKeys = Object.keys(expected).sort()
319
+ check(
320
+ JSON.stringify(actualKeys) === JSON.stringify(expectedKeys),
321
+ `Agent "${agentName}" permission.task keys mismatch: expected [${expectedKeys}], got [${actualKeys}]`,
322
+ )
323
+ for (const [key, val] of Object.entries(expected)) {
324
+ check(
325
+ actual[key] === val,
326
+ `Agent "${agentName}" permission.task.${key}: expected "${val}", got "${actual[key]}"`,
327
+ )
328
+ }
329
+ }
330
+ }
331
+
168
332
  // Summary
169
333
  console.log(`\n${'='.repeat(50)}`)
170
334
  const status = failures === 0 ? '✅ PASSED' : `❌ FAILED (${failures}/${checks} checks failed)`
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: aphrodite
3
3
  description: "Frontend specialist — React 19, TypeScript strict, WCAG accessibility, responsive design, TDD, modern API patterns, deprecated npm detection. Calls apollo for discovery, sends to themis for review."
4
- mode: primary
4
+ mode: all
5
5
  reasoning_effort: medium
6
6
  permission:
7
7
  read: allow
@@ -11,6 +11,8 @@ permission:
11
11
  webfetch: allow
12
12
  glob: allow
13
13
  question: allow
14
+ task:
15
+ "*": deny
14
16
  temperature: 0.3
15
17
  steps: 50
16
18
  skills:
@@ -10,6 +10,8 @@ permission:
10
10
  webfetch: allow
11
11
  edit: deny
12
12
  bash: deny
13
+ task:
14
+ "*": deny
13
15
  temperature: 0.1
14
16
  steps: 30
15
17
  skills:
@@ -3,7 +3,7 @@ name: athena
3
3
  description: Strategic planner & architect — research-first, plan-only, never implements.
4
4
  Plans include quality gates (ruff/Biome, dep detection, LTS policy). Calls apollo
5
5
  for discovery.
6
- mode: primary
6
+ mode: all
7
7
  reasoning_effort: high
8
8
 
9
9
  steps: 30
@@ -21,6 +21,9 @@ permission:
21
21
  grep: allow
22
22
  glob: allow
23
23
  webfetch: allow
24
+ task:
25
+ "*": deny
26
+ apollo: allow
24
27
  ---
25
28
 
26
29
  ## When NOT to Use Athena
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: demeter
3
3
  description: "Database specialist — SQLAlchemy 2.0, Alembic, query optimization, N+1 prevention, TDD migrations, modern DB libs. Calls apollo for discovery, sends to themis."
4
- mode: primary
4
+ mode: all
5
5
  reasoning_effort: medium
6
6
  permission:
7
7
  read: allow
@@ -9,6 +9,8 @@ permission:
9
9
  edit: allow
10
10
  bash: allow
11
11
  webfetch: allow
12
+ task:
13
+ "*": deny
12
14
  temperature: 0.2
13
15
  steps: 40
14
16
  skills:
@@ -9,6 +9,8 @@ permission:
9
9
  webfetch: allow
10
10
  edit: deny
11
11
  bash: deny
12
+ task:
13
+ "*": deny
12
14
  temperature: 0.2
13
15
  steps: 30
14
16
  skills:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: hephaestus
3
3
  description: "AI tooling & pipelines specialist — LangChain/LangGraph chains, RAG architecture, vector stores, embedding strategies. Forges AI infrastructure. Calls apollo, sends to themis."
4
- mode: subagent
4
+ mode: all
5
5
  reasoning_effort: medium
6
6
  permission:
7
7
  read: allow
@@ -9,6 +9,8 @@ permission:
9
9
  edit: allow
10
10
  bash: allow
11
11
  webfetch: allow
12
+ task:
13
+ "*": deny
12
14
  temperature: 0.3
13
15
  steps: 40
14
16
  skills:
@@ -3,7 +3,7 @@ name: hermes
3
3
  description: Backend specialist — FastAPI, Python, async, TDD (RED→GREEN→REFACTOR),
4
4
  modern Python stdlib, obsolete lib detection. Calls apollo for discovery, sends
5
5
  to themis.
6
- mode: primary
6
+ mode: all
7
7
  reasoning_effort: medium
8
8
 
9
9
  mcp_tools:
@@ -25,6 +25,9 @@ permission:
25
25
  edit: allow
26
26
  webfetch: allow
27
27
  glob: allow
28
+ task:
29
+ "*": deny
30
+ apollo: allow
28
31
  ---
29
32
 
30
33
  ## Table of Contents
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: iris
3
3
  description: "GitHub operations specialist — branches, pull requests, issues, releases, tags. Called by zeus after review. Never pushes or merges without explicit human approval. Integrates with GitHub CLI (gh) for operations."
4
- mode: subagent
4
+ mode: all
5
5
  reasoning_effort: low
6
6
  permission:
7
7
  read: allow
@@ -11,6 +11,8 @@ permission:
11
11
  gh *: allow
12
12
  webfetch: allow
13
13
  edit: deny
14
+ task:
15
+ "*": deny
14
16
  temperature: 0.2
15
17
  steps: 25
16
18
  skills:
@@ -3,7 +3,7 @@ name: mnemosyne
3
3
  description: Memory bank quality owner — initializes .pantheon/memory-bank/, writes ADRs
4
4
  and task records on explicit request. Called by zeus. Never invoked automatically
5
5
  after phases.
6
- mode: primary
6
+ mode: all
7
7
  reasoning_effort: low
8
8
 
9
9
  steps: 25
@@ -35,6 +35,8 @@ permission:
35
35
  read: allow
36
36
  grep: allow
37
37
  edit: deny
38
+ task:
39
+ "*": deny
38
40
  ---
39
41
 
40
42
  ## ⚠️ SCOPE BOUNDARY
package/src/agents/nyx.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: nyx
3
3
  description: "Observability & monitoring specialist — OpenTelemetry tracing, token/cost tracking, agent performance analytics, LangSmith integration. Calls apollo for discovery, sends to themis."
4
- mode: subagent
4
+ mode: all
5
5
  reasoning_effort: low
6
6
  permission:
7
7
  read: allow
@@ -9,6 +9,8 @@ permission:
9
9
  bash: allow
10
10
  webfetch: allow
11
11
  edit: deny
12
+ task:
13
+ "*": deny
12
14
  temperature: 0.1
13
15
  steps: 25
14
16
  skills:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: prometheus
3
3
  description: "Infrastructure + model provider specialist — Docker, CI/CD, multi-model routing, cost optimization, provider abstraction"
4
- mode: subagent
4
+ mode: all
5
5
  reasoning_effort: medium
6
6
  permission:
7
7
  read: allow
@@ -9,6 +9,8 @@ permission:
9
9
  edit: allow
10
10
  bash: allow
11
11
  webfetch: allow
12
+ task:
13
+ "*": deny
12
14
  temperature: 0.2
13
15
  steps: 30
14
16
  skills:
@@ -1,12 +1,14 @@
1
1
  ---
2
2
  name: talos
3
3
  description: "Hotfix express lane — direct fixes for small bugs, CSS, typos, minor logic. No TDD ceremony, no orchestration overhead. Standalone, no subagents. Escalates complex issues to zeus."
4
- mode: subagent
4
+ mode: all
5
5
  reasoning_effort: low
6
6
  permission:
7
7
  read: allow
8
8
  edit: allow
9
9
  bash: allow
10
+ task:
11
+ "*": deny
10
12
  temperature: 0.3
11
13
  steps: 35
12
14
  skills:
@@ -1,13 +1,15 @@
1
1
  ---
2
2
  name: themis
3
3
  description: "Quality & security gate — Ruff/Biome linting, dead/legacy code detection, OWASP Top 10, coverage >80%, correctness, deprecation audit. Called by implementers; escalates blockers to zeus."
4
- mode: subagent
4
+ mode: all
5
5
  reasoning_effort: high
6
6
  permission:
7
7
  read: allow
8
8
  grep: allow
9
9
  bash: allow
10
10
  edit: deny
11
+ task:
12
+ "*": deny
11
13
  temperature: 0.1
12
14
  steps: 30
13
15
  skills: