Origin
Penster batch 20260909T000015: a task's ## File Scope was authored short (it named penster.yaml, an OpenAPI spec, but not api_gen.go nor the ~14 files importing its types). Penster's worker overlay treats scope as a hard fence — touching an out-of-scope file → escalate_to_supervisor → hold → ruling. The gap was discovered one file class at a time: three holds that could have been one.
Two observations from the Penster agent, assessed against core:
What is core's and what is not
## File Scope in Taskplane core drives lane affinity (extensions/taskplane/waves.ts:350-417) and is echoed into the merge prompt (merge.ts:678-700). The worker template only says "note out-of-scope discoveries in STATUS.md" (templates/agents/task-worker.md:286) — core never asks a worker to hold on a scope fence. The fence is a Penster overlay convention, and a good one; but "generated counterpart of an OpenAPI/proto/schema file" is toolchain knowledge core must not hard-code. Both items below stay project-agnostic.
1. Prompt contract — batch the escalation (cheap, ship first)
The engine cannot know which files a worker "can already see"; the worker does (a failed build lists them all). Make the contract explicit:
templates/agents/task-worker.md (escalation section): when escalating on a scope or permission fence, enumerate every file / file class you already know is affected — paths, not diffs — in ONE escalation; do not hold once per discovery. Note the outbox message cap (4 KB, agent-bridge-extension.ts) so long lists are summarised by directory/glob.
extensions/taskplane/supervisor-primer.md (ruling recipe): if the escalation lists N files, rule on all N in one ruling; ask (type="query") for the full list before ruling if it is truncated.
- Docs: describe the scope-fence pattern as an opt-in overlay convention (
docs/reference/task-format.md File Scope section) so other projects adopt it deliberately.
2. Engine — coupled-scope warning at discovery / doctor (config-declared, generic)
Let the project declare its own generator couplings:
"taskRunner": {
"fileScope": {
"coupled": [
{ "when": "api/openapi.yaml", "require": ["internal/api_gen.go", "internal/handlers/**"] },
{ "when": "proto/**/*.proto", "require": ["gen/**"] }
]
}
}
Discovery (and taskplane doctor) warns when a task's File Scope matches a when but omits any require: "TP-2101: api/openapi.yaml is in scope; internal/api_gen.go and internal/handlers/** are not — scope fences will hold on them." Uses the existing scopesOverlap (waves.ts:315-335) for matching. Deterministic, no toolchain knowledge in core; the project maintains the list.
Deferred variant (only if the declared form proves insufficient): a project-supplied expandCommand that doctor --scope <task> runs to derive the real dependency closure and diff it against the declared scope. More power, more surface.
Related
Acceptance
- Worker template + primer updated; a fixture escalation listing 3 files produces one hold, one ruling.
taskRunner.fileScope.coupled parsed (config-schema/loader, YAML alias file_scope.coupled), validated, documented in the config reference.
- Discovery warning +
doctor warning with a unit test (scope matches when, misses a require → warning; all present → silent).
Origin
Penster batch 20260909T000015: a task's
## File Scopewas authored short (it namedpenster.yaml, an OpenAPI spec, but notapi_gen.gonor the ~14 files importing its types). Penster's worker overlay treats scope as a hard fence — touching an out-of-scope file →escalate_to_supervisor→ hold → ruling. The gap was discovered one file class at a time: three holds that could have been one.Two observations from the Penster agent, assessed against core:
What is core's and what is not
## File Scopein Taskplane core drives lane affinity (extensions/taskplane/waves.ts:350-417) and is echoed into the merge prompt (merge.ts:678-700). The worker template only says "note out-of-scope discoveries in STATUS.md" (templates/agents/task-worker.md:286) — core never asks a worker to hold on a scope fence. The fence is a Penster overlay convention, and a good one; but "generated counterpart of an OpenAPI/proto/schema file" is toolchain knowledge core must not hard-code. Both items below stay project-agnostic.1. Prompt contract — batch the escalation (cheap, ship first)
The engine cannot know which files a worker "can already see"; the worker does (a failed build lists them all). Make the contract explicit:
templates/agents/task-worker.md(escalation section): when escalating on a scope or permission fence, enumerate every file / file class you already know is affected — paths, not diffs — in ONE escalation; do not hold once per discovery. Note the outbox message cap (4 KB,agent-bridge-extension.ts) so long lists are summarised by directory/glob.extensions/taskplane/supervisor-primer.md(ruling recipe): if the escalation lists N files, rule on all N in one ruling; ask (type="query") for the full list before ruling if it is truncated.docs/reference/task-format.mdFile Scope section) so other projects adopt it deliberately.2. Engine — coupled-scope warning at discovery /
doctor(config-declared, generic)Let the project declare its own generator couplings:
Discovery (and
taskplane doctor) warns when a task's File Scope matches awhenbut omits anyrequire: "TP-2101:api/openapi.yamlis in scope;internal/api_gen.goandinternal/handlers/**are not — scope fences will hold on them." Uses the existingscopesOverlap(waves.ts:315-335) for matching. Deterministic, no toolchain knowledge in core; the project maintains the list.Deferred variant (only if the declared form proves insufficient): a project-supplied
expandCommandthatdoctor --scope <task>runs to derive the real dependency closure and diff it against the declared scope. More power, more surface.Related
Acceptance
taskRunner.fileScope.coupledparsed (config-schema/loader, YAML aliasfile_scope.coupled), validated, documented in the config reference.doctorwarning with a unit test (scope matcheswhen, misses arequire→ warning; all present → silent).