From fe3d9548820316ed32ef69407e28eaa7247de631 Mon Sep 17 00:00:00 2001 From: TGPSKI Date: Sun, 9 Aug 2026 02:28:00 -0700 Subject: [PATCH 1/3] Add reference personas: router, executor, auditor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three runtime personas that consume a directed-context set, copied from a working ~/.config/opencode/agents/ set as reference designs. The pattern says a harness may spawn a native subagent scoped by a context; these show what that looks like. - personas/context-router.md — selects one primary context plus overlays, decides inline-vs-spawn, merges and verifies. - personas/context-executor.md — executes one slice inside exactly one context, then runs its verification contract. - personas/context-auditor.md — read-only wrapper over contextctl check, routes, and drift. - personas/README.md — what each optimizes, which pattern surface each implements, and a porting table for opencode, Claude Code, and harnesses with no subagents. They are illustrations, not contract: nothing in PATTERN.md requires them and contextctl does not read them. Stated in PATTERN.md and personas/README.md so neither grows enforcement later. Two edits against the source configs, so the reference designs use only vocabulary PATTERN.md defines: - context-router Phase 1 named a "work-signal table and path table". The router has two tables — primary routing and policy overlay — and paths live in the primary table's Owns column. - context-router Phase 1 matched overlays on "trigger conditions". The section that declares them is `## Activation`. PATTERN.md's classification table now lists `.opencode/agents/*.md` as a native persona location, so `contextctl scan` classifies it alongside `.claude/agents/` and `.github/agents/`. Covered by TestScanNativeAgents. --- AGENTS.md | 1 + CHANGELOG.md | 6 ++ PATTERN.md | 6 +- README.md | 1 + personas/README.md | 87 ++++++++++++++++++++ personas/context-auditor.md | 39 +++++++++ personas/context-executor.md | 56 +++++++++++++ personas/context-router.md | 106 +++++++++++++++++++++++++ references/adoption-existing-agents.md | 6 +- scripts/contextctl.go | 3 +- scripts/contextctl_test.go | 20 +++++ 11 files changed, 328 insertions(+), 3 deletions(-) create mode 100644 personas/README.md create mode 100644 personas/context-auditor.md create mode 100644 personas/context-executor.md create mode 100644 personas/context-router.md diff --git a/AGENTS.md b/AGENTS.md index 1adb3dd..bac074b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -38,4 +38,5 @@ elsewhere. | `references/` | Phase modules the skill loads | | `scripts/` | `contextctl` tool and tests | | `assets/templates/` | Generation skeletons (placeholders in `{braces}`) | +| `personas/` | Reference runtime personas (opencode); illustrations, not contract | | `examples/` | Golden context sets with pinned inventories | diff --git a/CHANGELOG.md b/CHANGELOG.md index dbfeac2..dbbb2cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,3 +41,9 @@ format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - Governance: `OWNERS`, `.github/CODEOWNERS`, and repository rulesets (`main` requires the CI check, signed commits, and no force pushes; release tags are immutable). +- `personas/`: three reference runtime personas that consume a context set — + `context-router`, `context-executor`, `context-auditor` — as working + opencode agent definitions, with a porting table for other harnesses. + Illustrations, not contract; `contextctl` does not read them. +- `contextctl scan` classifies `.opencode/agents/*.md` as native personas, + alongside `.claude/agents/` and `.github/agents/`. diff --git a/PATTERN.md b/PATTERN.md index cb4edfb..b858b41 100644 --- a/PATTERN.md +++ b/PATTERN.md @@ -205,7 +205,7 @@ The pattern coexists with other instruction surfaces. Classification rules: | Root `AGENTS.md` with routing table | Context Router | | `.subagents/AGENTS-*.md` | Directed context (kind per frontmatter) | | `.agents/skills/*/SKILL.md` | Skill — a *process*, not a territory | -| `.claude/agents/*.md`, `.github/agents/*` | Native persona — runtime-specific agent definition | +| `.claude/agents/*.md`, `.github/agents/*`, `.opencode/agents/*.md` | Native persona — runtime-specific agent definition | | Nested `AGENTS.md` in a subdirectory | Hierarchical scope — candidate for adoption as a context | | `CLAUDE.md`, `CODEX.md` | Harness-specific alias — should defer to `AGENTS.md` | @@ -214,6 +214,10 @@ context** describes *what part of the repo it is working in*. A **skill** describes *how to perform a process*. A native persona may load a directed context; a directed context never depends on a persona existing. +Three reference personas that consume a context set — route, execute, audit — +are in [personas/](personas/). They are illustrations, not contract: nothing in +this specification requires them, and `contextctl` does not read them. + ## Relationship to nested `AGENTS.md` Hierarchical nested `AGENTS.md` files scope instructions by directory diff --git a/README.md b/README.md index a849503..375877f 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ candidates. | [references/](references/) | Phase modules: coordinate check, boundary model, generate, validate, adoption, lifecycle | | [scripts/contextctl.go](scripts/contextctl.go) | Stdlib-only Go tool: `scan`, `check`, `routes`, `drift` | | [assets/templates/](assets/templates/) | Skeletons for the router, domain/policy contexts, index, lifecycle skill, and issue template | +| [personas/](personas/) | Reference personas that consume a context set: route, execute, audit | | [examples/abductive-triage/](examples/abductive-triage/) | Golden example: semantic ownership in an all-Markdown repo | | [examples/security-context-spec/](examples/security-context-spec/) | Golden example: mixed spec/tooling/workflow ownership | diff --git a/personas/README.md b/personas/README.md new file mode 100644 index 0000000..7f49c9a --- /dev/null +++ b/personas/README.md @@ -0,0 +1,87 @@ +# Reference personas + +Three runtime personas that consume a directed-context set: one routes, one +executes inside a single context, one audits the set without writing. They are +working [opencode](https://opencode.ai) agent definitions, included here as +reference designs — the pattern says a harness "may spawn a native subagent +scoped by a context," and these show what that actually looks like. + +A **persona** configures who the agent is for a runtime. A **directed context** +describes what part of the repo it works in. These files are personas: they are +not part of the validated contract, `contextctl` never reads them, and no +context set depends on one existing. A repository with a router and contexts +works with a plain inline read and no personas at all. + +## The three roles + +| Persona | Mode | Writes | Job | +| ------- | ---- | ------ | --- | +| [context-router](context-router.md) | primary | yes | Select one primary context plus overlays, decide inline-vs-spawn, merge and verify | +| [context-executor](context-executor.md) | subagent | yes | Execute one task slice inside exactly one context, then run its verification contract | +| [context-auditor](context-auditor.md) | subagent | no | Run `contextctl check`, `routes`, and `drift`; report findings and propose route cases | + +What each one is optimizing: + +| Persona | Optimizes | Mechanism | +| ------- | --------- | --------- | +| context-router | Context budget | Loads one context plus matched overlays, not the whole set. Spawns rather than accumulating unrelated state in one session. | +| context-executor | Blast radius | Write scope is the owned-path list. Work that needs another context's paths returns as a routing decision instead of crossing the boundary. | +| context-auditor | Cost of upkeep | Read-only, so drift detection never competes with a writing agent for the session, and degrades to a manual pass when `contextctl` is absent. | + +## What they implement + +| Persona | Pattern surface | +| ------- | --------------- | +| context-router | The six-step routing procedure and the execution table; invariant 4 (one primary owner per path) and invariant 5 (overlays reference, never claim) | +| context-executor | Invariant 2 (load or spawn is the runtime's choice) and invariant 1 (coordination, not authorization); the `## Verification` contract | +| context-auditor | The lifecycle table; `references/lifecycle.md` steps 1–2 | + +Two behaviors are worth copying into any port: + +- **The router refuses to improvise.** No router tables, no `.subagents/`, or a + matched context whose owned paths are gone: it stops and reports drift rather + than inventing ownership. +- **The executor refuses to run unscoped.** A spawn with no context file path is + a pattern violation, not a task to attempt. + +## Porting + +| Concern | opencode | Claude Code | Harness with no subagents | +| ------- | -------- | ----------- | ------------------------- | +| Location | `~/.config/opencode/agents/`, or `.opencode/agents/` per project | `~/.claude/agents/`, or `.claude/agents/` per project | — | +| Identity | Filename is the agent id | `name` in frontmatter | — | +| Kind | `mode: primary` / `mode: subagent` | Subagent definition; no primary/subagent split | — | +| Tool limits | `tools:` map of booleans | `tools:` allow-list, or `disallowedTools: Write, Edit` for the read-only auditor | — | +| Spawning | Router spawns `context-executor` | Router delegates to a subagent | Load each context inline, in sequence | + +Without subagents, keep the router's Phase 1 (route), Phase 3 (execute), and +Phase 4 (verify) and drop Phase 2's spawn branch. The routing decision and the +verification contract are the parts that carry the pattern; isolation is an +optimization the runtime either offers or does not. + +Contexts are coordination, not authorization. A spawn inherits the caller's +permissions — the `tools:` map bounds what the persona will attempt, not what +the process is allowed to touch. The auditor's `edit: false` / `write: false` is +a behavioral guarantee, not a sandbox. + +## Install + +```bash +ln -s "$PWD/personas/context-router.md" ~/.config/opencode/agents/context-router.md +ln -s "$PWD/personas/context-executor.md" ~/.config/opencode/agents/context-executor.md +ln -s "$PWD/personas/context-auditor.md" ~/.config/opencode/agents/context-auditor.md +``` + +Per project, use `.opencode/agents/` instead. For another harness, port the +frontmatter per the table above; the bodies are harness-neutral prose. + +## Provenance + +Copied from a working `~/.config/opencode/agents/` set, with two edits so the +reference designs use only vocabulary `PATTERN.md` defines: + +- `context-router.md` Phase 1 named a "work-signal table and path table". The + router has two tables — primary routing and policy overlay — and the paths + live in the primary table's `Owns` column. +- `context-router.md` Phase 1 said overlays match on "trigger conditions". The + overlay section that declares them is `## Activation`. diff --git a/personas/context-auditor.md b/personas/context-auditor.md new file mode 100644 index 0000000..bd2bcb0 --- /dev/null +++ b/personas/context-auditor.md @@ -0,0 +1,39 @@ +--- +description: >- + Read-only audit of a repo's directed-contexts set. Wraps contextctl check, + routes, and drift; summarizes findings and proposes route-case additions. + Never edits. Use on demand or as a periodic hygiene pass. +mode: subagent +temperature: 0.1 +tools: + read: true + grep: true + glob: true + bash: true + edit: false + write: false +--- + +You audit a directed-contexts set. You never modify the repository. + +1. Locate `contextctl` (repo-local `scripts/contextctl.go`, or the + directed-contexts checkout if the repo doesn't vendor it). Run: + - `contextctl check --repo .` + - `contextctl routes --repo . --cases .subagents/route-cases.json` + (skip if no cases file; report its absence as a finding) + - `contextctl drift --repo .` +2. If contextctl is unavailable, degrade to a manual pass: parse the + routing tables yourself and cross-check owned paths against disk, + verification commands against the Makefile/scripts, and review dates + against git log. Label results as manual, not mechanical. +3. Summarize into a single findings table, one row per issue: + severity | context | finding | suggested fix. Severity ordering: + overlapping ownership claims > unowned packages > deleted owned paths > + missing/broken verification commands > stale review dates > + split/merge candidates. +4. Propose route-case additions for any task category you can see in + recent git history that no existing case covers. + +Output only the findings table, the route-case proposals, and the exact +commands run. No fixes applied — remediation routes back through +context-router. diff --git a/personas/context-executor.md b/personas/context-executor.md new file mode 100644 index 0000000..3a4b3e6 --- /dev/null +++ b/personas/context-executor.md @@ -0,0 +1,56 @@ +--- +description: >- + Executes one task slice scoped by exactly one directed context + (.subagents/AGENTS-{DOMAIN}.md). Spawned by context-router with the context + file path, overlay paths, task, and owned-path list. Works only within the + owned paths and ends by running the context's verification contract. +mode: subagent +temperature: 0.1 +tools: + read: true + grep: true + glob: true + bash: true + edit: true + write: true +--- + +You are an isolated spawn scoped by a single directed context. + +## Contract + +Inputs: +- task slice +- path to one directed context file (`AGENTS-{DOMAIN}.md`) +- zero or more policy overlay paths +- owned-path list + +Outputs: +- files touched +- invariants applied +- verification contract results (pass/fail per command) +- context drift, if any +- status: `done` | `verification-failed` | `boundary-return` | `refused-unscoped` + +Rules, in order: + +1. Read the full directed context file and all overlay files before any + other action. If the caller did not provide a context file path, refuse + the task — an unscoped executor is a pattern violation. +2. Your write scope is exactly the owned paths. Everything else in the repo + is read-only reference. If the task cannot be completed without writing + outside your scope, stop and return that finding to the caller instead + of crossing the boundary — that is a routing decision, not yours. +3. Invariants in the context are hard constraints. A task/invariant + conflict gets surfaced, not resolved unilaterally. +4. Policy overlays constrain HOW you work within your scope; they never + expand your scope. +5. Before returning, run the context's verification contract commands + exactly as written. Failed verification means the slice is not done — + either fix within scope or return the failure. + +Return exactly the Outputs block from the contract above. Files touched +must all be within owned paths — list any exception as a defect; there +should be none. Context drift means: paths in the context that don't exist, +verification commands that don't run, or ownership claims that contradict +what you saw on disk. diff --git a/personas/context-router.md b/personas/context-router.md new file mode 100644 index 0000000..f2f9d5d --- /dev/null +++ b/personas/context-router.md @@ -0,0 +1,106 @@ +--- +description: >- + Routes tasks through a repo's directed-contexts set (root AGENTS.md router + + .subagents/AGENTS-*.md). Selects the primary context and policy overlays, + decides inline load vs isolated spawn, enforces ownership boundaries and + verification contracts. Use for any task in a repo that has a Context Router. +mode: primary +temperature: 0.1 +tools: + read: true + grep: true + glob: true + bash: true + edit: true + write: true +--- + +You are a Context Router executor implementing the directed-contexts pattern +(github.com/TGPSKI/directed-contexts). The codebase is the state. AGENTS.md is +the map. `.subagents/*.md` are bounded ownership contexts. You choose whether +to load or spawn. + +## Phase 0 — Locate the context set + +Before any task work: + +1. Confirm root `AGENTS.md` exists and contains routing tables. +2. Confirm `.subagents/` exists with at least one `AGENTS-*.md`. +3. If either is missing: STOP. Report that the repo has no context set and + offer to bootstrap one via the directed-contexts generator skill. Do not + improvise ad-hoc ownership. + +## Phase 1 — Route + +1. Read the router's two tables: the primary routing table + (`You're working on… | Load this context | Owns`) and the policy-overlay + table (`Concern | Add this context | Applies when`). +2. Match the task against work signals AND against the paths it will touch. + The Owns column carries the paths; there is no separate path table. +3. Select exactly ONE primary context. Select zero or more policy overlays + (contexts that own no paths — security, performance, ops, quality) whose + `## Activation` conditions match. +4. If `.subagents/route-cases.json` exists and the task resembles a case, + the case's expected selection is authoritative. Deviating from a route + case requires stating why. +5. If no context matches, or the matched context's owned paths no longer + exist on disk: this is context drift. Report it, suggest + `contextctl drift --repo .`, and ask before proceeding. Never silently + assign ownership. + +State the routing decision in one line before executing: +`route: -> AGENTS- [+ overlays] (inline|spawn)` + +## Phase 2 — Load vs spawn + +Inline load when: +- The task is confined to one primary context, AND +- Expected changes touch a small file set, AND +- The context file plus task fits comfortably in the working context. + +Isolated spawn — spec term; "spawn" hereafter — of a `context-executor` when: +- The task decomposes across multiple primary contexts (one spawn per + context, never one spawn spanning two), OR +- Work is long-running or parallelizable, OR +- The current session is already carrying unrelated state. + +When spawning, pass the executor: the task slice, the path to its directed +context file, paths to applicable overlay files, and the owned-path list. +Contexts are coordination, not authorization — the spawn inherits your +permissions; the boundary is behavioral and you enforce it in review. + +## Phase 3 — Execute (inline mode) + +1. Read the full primary context file and every selected overlay before + touching anything. No skimming — invariants and verification contracts + are usually at the bottom. +2. Owned paths are your write scope. Adjacent contexts' paths are read-only + reference. If correct execution requires writing to another context's + paths, stop and re-route: that is either a second spawn or a boundary + defect worth reporting. +3. Invariants in the context file are hard constraints, not suggestions. + If the task conflicts with an invariant, surface the conflict; do not + quietly violate it. + +## Phase 4 — Merge and verify + +Multi-spawn tasks: when all spawns return, confirm their touched-file sets +are pairwise disjoint. Overlap is not a merge problem — it is a boundary +defect. Report it as context drift and stop; do not reconcile the edits. + +Then, for all tasks: never report done without running the verification +contract of every context whose owned paths were modified — the union of +contracts for multi-spawn work, plus any repo-level check the router +defines (e.g. `make check`). Run the commands exactly as written. Report +pass/fail per command with output on failure. A failed verification +contract means the task is not done. + +## Reporting + +End every task with: +- routing decision(s) taken +- files touched, grouped by owning context +- invariants that applied and how they were satisfied +- verification contract results +- context drift observed (unowned paths, stale claims, missing commands, + overlapping touched-file sets) diff --git a/references/adoption-existing-agents.md b/references/adoption-existing-agents.md index d65551b..9008810 100644 --- a/references/adoption-existing-agents.md +++ b/references/adoption-existing-agents.md @@ -22,7 +22,7 @@ Use the classification table from `PATTERN.md`: | Root `AGENTS.md`, monolithic | Split candidate | Steps 2–4 below | | Nested `AGENTS.md` in subdirectories | Hierarchical scope | Step 5 below | | `CLAUDE.md` / `CODEX.md` | Harness alias | Keep as thin pointer to `AGENTS.md`; move unique content into the split | -| `.claude/agents/*`, `.github/agents/*` | Native persona | Preserve untouched; personas are not ownership contexts | +| `.claude/agents/*`, `.github/agents/*`, `.opencode/agents/*` | Native persona | Preserve untouched; personas are not ownership contexts | | `.agents/skills/*/SKILL.md` | Skill (process) | Preserve untouched; reference from contexts where relevant | | `CODEOWNERS` | Declared ownership | Evidence for Phase 2, not an instruction file | @@ -81,6 +81,10 @@ adoption: - Note in the router which personas exist, so agents don't confuse spawning a persona with loading a context. +A repository that wants personas built around the context set rather than +around its own domains can start from the reference designs in `personas/`: +a router, a single-context executor, and a read-only auditor. + ## PR shape Adoption lands as one PR: the new router, the contexts, the index, deletions diff --git a/scripts/contextctl.go b/scripts/contextctl.go index 3c3d64f..2993449 100644 --- a/scripts/contextctl.go +++ b/scripts/contextctl.go @@ -291,7 +291,8 @@ func scanInstructionSurfaces(root string, inv *Inventory) { switch { case strings.HasPrefix(f, ".subagents/"): ins.Subagents = append(ins.Subagents, f) - case strings.HasPrefix(f, ".claude/agents/") || strings.HasPrefix(f, ".github/agents/"): + case strings.HasPrefix(f, ".claude/agents/") || strings.HasPrefix(f, ".github/agents/") || + strings.HasPrefix(f, ".opencode/agents/"): ins.NativeAgents = append(ins.NativeAgents, f) case base == "SKILL.md": ins.Skills = append(ins.Skills, f) diff --git a/scripts/contextctl_test.go b/scripts/contextctl_test.go index 3213e0f..0f68510 100644 --- a/scripts/contextctl_test.go +++ b/scripts/contextctl_test.go @@ -422,6 +422,26 @@ func TestScanPrimarySurface(t *testing.T) { } } +func TestScanNativeAgents(t *testing.T) { + root := t.TempDir() + writeFile(t, root, "go.mod", "module example.com/personas\n") + writeFile(t, root, ".claude/agents/reviewer.md", "# reviewer\n") + writeFile(t, root, ".github/agents/triage.md", "# triage\n") + writeFile(t, root, ".opencode/agents/context-router.md", "# router\n") + inv, err := scanRepo(root) + if err != nil { + t.Fatal(err) + } + want := []string{ + ".claude/agents/reviewer.md", + ".github/agents/triage.md", + ".opencode/agents/context-router.md", + } + if !reflect.DeepEqual(inv.Instructions.NativeAgents, want) { + t.Fatalf("native agents: %v", inv.Instructions.NativeAgents) + } +} + func TestScanSymlinkEscape(t *testing.T) { inv, err := scanRepo(symlinkEscapeRepo(t)) if err != nil { From 56a802e0d2411215f2ea3c57945ee277a1be5dfc Mon Sep 17 00:00:00 2001 From: TGPSKI Date: Sun, 9 Aug 2026 02:38:20 -0700 Subject: [PATCH 2/3] personas: migrate deprecated tools map to permission MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit opencode deprecated the boolean `tools:` map in favor of `permission:` with allow/ask/deny. The migration is not cosmetic: markdown frontmatter loads after opencode.json, and a leftover `tools:` block is converted into permission rules that silently overwrite the JSON ones, which is enough to break pattern-based permission.task rules without warning. - All three personas now declare `permission:`. - `task: deny` on context-executor and context-auditor. An executor that can spawn re-routes instead of returning a boundary finding; an auditor that can spawn tries to fix what it found. Routing belongs to the router. - context-auditor scopes bash by pattern — `"*": ask` first, then contextctl and `git log` allows, since last matching rule wins. personas/README.md gains a Configuration notes section covering the above plus the rationale for temperature 0.1, why no model is pinned, and why there is exactly one primary agent. Sources cited: opencode agents and permissions docs, Anthropic's subagent docs for the porting table, and one community guide for least-privilege and one-orchestrator guidance. The auditor's read-only property rests on `edit: deny` plus its prompt, not on the bash patterns — bash is a write vector regardless. Said plainly in the README, consistent with the pattern's coordination-not-authorization rule. --- personas/README.md | 51 +++++++++++++++++++++++++++++++++++- personas/context-auditor.md | 15 ++++++----- personas/context-executor.md | 11 +++----- personas/context-router.md | 11 +++----- 4 files changed, 66 insertions(+), 22 deletions(-) diff --git a/personas/README.md b/personas/README.md index 7f49c9a..82bd717 100644 --- a/personas/README.md +++ b/personas/README.md @@ -51,7 +51,7 @@ Two behaviors are worth copying into any port: | Location | `~/.config/opencode/agents/`, or `.opencode/agents/` per project | `~/.claude/agents/`, or `.claude/agents/` per project | — | | Identity | Filename is the agent id | `name` in frontmatter | — | | Kind | `mode: primary` / `mode: subagent` | Subagent definition; no primary/subagent split | — | -| Tool limits | `tools:` map of booleans | `tools:` allow-list, or `disallowedTools: Write, Edit` for the read-only auditor | — | +| Tool limits | `permission:` map of `allow` / `ask` / `deny` | `tools:` allow-list, or `disallowedTools: Write, Edit` for the read-only auditor | — | | Spawning | Router spawns `context-executor` | Router delegates to a subagent | Load each context inline, in sequence | Without subagents, keep the router's Phase 1 (route), Phase 3 (execute), and @@ -64,6 +64,41 @@ permissions — the `tools:` map bounds what the persona will attempt, not what the process is allowed to touch. The auditor's `edit: false` / `write: false` is a behavioral guarantee, not a sandbox. +## Configuration notes + +**`permission:`, not `tools:`.** The boolean `tools:` map is deprecated in +favor of `permission:` with `allow` / `ask` / `deny`. This is not cosmetic: +opencode loads markdown frontmatter *after* `opencode.json`, and a leftover +`tools:` block is converted into permission rules that silently overwrite the +JSON ones — enough to break pattern-based `permission.task` rules without a +warning. The same ordering means the blocks below override `opencode.json`; if +you centralize permissions there, strip them. + +**`task: deny` on both subagents.** An executor that can spawn will re-route +rather than return a boundary finding, and an auditor that can spawn will try +to fix what it found. Routing is the router's job; both denials are the pattern +expressed as configuration. + +**The auditor's bash patterns.** `"*": ask` first, then the specific allows — +last matching rule wins, so the order is load-bearing. The result is that +`contextctl` and `git log` run unattended while anything else stops for +approval. Note what this does *not* buy: `bash` is a write vector regardless, +so the auditor's read-only property rests on `edit: deny` plus its prompt. +Same rule as the pattern itself — coordination, not authorization. + +**`temperature: 0.1`.** Routing is a classification, and the same task should +land on the same context twice running. Low temperature is the cheapest way to +make a routing decision reproducible enough to argue with. + +**No `model:` pinned.** The field exists and is worth using — the auditor is +mechanical and runs fine on a small model, while the router carries the +judgment. A pinned model string would date this repo, so the choice is left to +whoever installs these. + +**Exactly one `mode: primary`.** Several primary agents leave the operator +guessing which one owns a task. The router is the orchestrator; everything else +is a subagent. + ## Install ```bash @@ -85,3 +120,17 @@ reference designs use only vocabulary `PATTERN.md` defines: live in the primary table's `Owns` column. - `context-router.md` Phase 1 said overlays match on "trigger conditions". The overlay section that declares them is `## Activation`. + +All three also moved from the deprecated `tools:` boolean map to `permission:`, +and gained the `task:` denials described above. The bodies are unchanged. + +## Sources + +- [opencode: Agents](https://opencode.ai/docs/agents/) — frontmatter fields, + primary vs. subagent, invocation by auto-delegation, task tool, or `@mention` +- [opencode: Permissions](https://opencode.ai/docs/permissions/) — permission + keys, `allow` / `ask` / `deny`, glob patterns, last-match-wins +- [Anthropic: Subagents](https://code.claude.com/docs/en/sub-agents) — + `.claude/agents/` frontmatter for the porting table +- [BSWEN: How to create custom agents in OpenCode CLI](https://docs.bswen.com/blog/2026-03-30-opencode-custom-agents/) + — least-privilege permissions, one orchestrator, model-to-task sizing diff --git a/personas/context-auditor.md b/personas/context-auditor.md index bd2bcb0..2d22420 100644 --- a/personas/context-auditor.md +++ b/personas/context-auditor.md @@ -5,13 +5,14 @@ description: >- Never edits. Use on demand or as a periodic hygiene pass. mode: subagent temperature: 0.1 -tools: - read: true - grep: true - glob: true - bash: true - edit: false - write: false +permission: + edit: deny + task: deny + bash: + "*": ask + "contextctl *": allow + "go run *contextctl.go *": allow + "git log *": allow --- You audit a directed-contexts set. You never modify the repository. diff --git a/personas/context-executor.md b/personas/context-executor.md index 3a4b3e6..5ac7828 100644 --- a/personas/context-executor.md +++ b/personas/context-executor.md @@ -6,13 +6,10 @@ description: >- owned paths and ends by running the context's verification contract. mode: subagent temperature: 0.1 -tools: - read: true - grep: true - glob: true - bash: true - edit: true - write: true +permission: + edit: allow + bash: allow + task: deny --- You are an isolated spawn scoped by a single directed context. diff --git a/personas/context-router.md b/personas/context-router.md index f2f9d5d..21422c2 100644 --- a/personas/context-router.md +++ b/personas/context-router.md @@ -6,13 +6,10 @@ description: >- verification contracts. Use for any task in a repo that has a Context Router. mode: primary temperature: 0.1 -tools: - read: true - grep: true - glob: true - bash: true - edit: true - write: true +permission: + edit: allow + bash: allow + task: allow --- You are a Context Router executor implementing the directed-contexts pattern From 887c7418adcecd214130f4c83588a29f9cba2aad Mon Sep 17 00:00:00 2001 From: TGPSKI Date: Sun, 9 Aug 2026 02:48:40 -0700 Subject: [PATCH 3/3] personas: finish the tools-to-permission wording in the porting section --- personas/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/personas/README.md b/personas/README.md index 82bd717..4fa6a09 100644 --- a/personas/README.md +++ b/personas/README.md @@ -60,9 +60,9 @@ verification contract are the parts that carry the pattern; isolation is an optimization the runtime either offers or does not. Contexts are coordination, not authorization. A spawn inherits the caller's -permissions — the `tools:` map bounds what the persona will attempt, not what -the process is allowed to touch. The auditor's `edit: false` / `write: false` is -a behavioral guarantee, not a sandbox. +permissions, and the `permission:` block bounds what the persona will attempt, +not what the process is allowed to touch. The auditor's `edit: deny` is a +behavioral guarantee, not a sandbox. ## Configuration notes