You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Today the task abstraction is input/output schemas + a free-form brief string. The schemas constrain data shape; the brief carries all behavioral convention — what to do, which artifacts to consult, where to write back, when to stop, how to attribute work.
Three failure modes show up once you run more than a handful of tasks:
The same workflow is re-authored every time.tools/src/tasks/fulfill-brief.ts hardcodes a 7-step PR workflow keyed off a GitHub issue. The continuation case ("apply review feedback to an existing PR") needs another helper with its own hardcoded workflow. The assess flow has a third. Each new pattern is another one-off helper, accumulating as tribal knowledge under tools/.
The runtime instructor and brief overlap.libs/pi-extension/src/runtime/runtime-instructor.ts already says "use moltnet_create_entry, sign every commit, use gh with App token." Any brief I write tends to repeat fragments of this. Two sources of truth that drift.
The brief is the load-bearing element. Free-form text puts the interpretation burden on the model. Successful runs depend on careful prompt authoring every time; conventions are invisible until they're broken.
The conventions are minimal not because the system is permissive, but because the system has no place to put them yet.
Out of scope (rejected alternatives)
Encoding workflows in skills. Skills (.claude/skills/, .agents/skills/) are operator-side advisory content bound to a human/agent identity. They aren't versioned with the task input — the inputCid is what audits a task, and a skill change wouldn't propagate to that. Wrong layer.
Adding an operation discriminator to task input schemas (fulfill_brief.operation: 'open_pr' | 'update_pr' | 'comment_on_pr'). Bakes opinionated workflow vocabulary into the backend. Explodes the schema, couples our task-types-lib to one team's conventions, makes the backend "a garbage bin" of named patterns.
Splitting fulfill_brief into multiple task types per workflow. Same problem at a different layer. Task type proliferation that the daemon, executor, and registry all have to learn about.
Proposal
Add a templates resource as a separate layer above the existing task types. Templates are data; they compile to a task input via simple substitution. The backend's job is storage + serving + per-team scoping; it has no opinion on template content.
Shape
{
"templateId": "pr-continuation-v1",
"taskType": "fulfill_brief",
"scope": "team:6743b4b1-..."|"global",
"params": {
"targetTaskId": { "type": "uuid", "required": true },
"reviewTaskId": { "type": "uuid", "required": false }
},
"brief": "Continue PR linked from task {{targetTaskId}}. Read its accepted attempt for the PR url and branch. {{#if reviewTaskId}}Address concerns from review task {{reviewTaskId}}. {{/if}}Push to the existing branch; do NOT open a new PR. Sign each commit.",
"defaultReferences": [
{ "taskId": "{{targetTaskId}}", "role": "target_source" },
{ "taskId": "{{reviewTaskId}}", "role": "judged_work", "when": "reviewTaskId" }
],
"defaultSuccessCriteria": null
}
A template names a taskType it compiles into, declares its params, and carries the brief text with placeholder substitution.
Two-tier registry
Global: small curated set authored by the project. Ships with @moltnet/tasks or a sibling lib. Examples: bug-fix-from-issue-v1, pr-continuation-v1. 5-10 total max — anything beyond that belongs in team registries.
Team: team-owned, scoped to teamId. Backend has a task_templates table keyed on (teamId | "global", templateId, version) with a publish endpoint. Like Postman collections — teams build their own playbook.
task from-template is the operator-side helper: load the template, validate the params against the declared shape, substitute into the brief, POST as the named taskType.
Versioning
Templates are immutable per (scope, templateId, version). Publishing a new version of pr-continuation-v1 is pr-continuation-v2. The inputCid on a task pins the compiled brief, so revising a template doesn't retroactively change what prior tasks were judged against.
What the backend validates
Only the template schema — that params is well-typed, brief is a string, defaultReferences match TaskRef's shape, taskType is registered. No semantic validation. The backend never knows that "update_pr" is a concept.
What tools/src/tasks/ becomes
Discovery loop, as the user described it:
Author a one-off TS helper when a real need surfaces (current pattern — fine).
Use it 2-3 times; let the prompt stabilize.
Extract the prompt + param shape into a template JSON.
Publish to the team registry.
The TS helper becomes a thin shim around moltnet task from-template.
The library never needs to know pr-continuation-v1 exists. The CLI's task from-template works for any registered template authored by anyone.
Migration
tools/src/tasks/fulfill-brief.ts → its prompt becomes bug-fix-from-issue-v1 template. The TS file shrinks to "fetch issue, populate params, call from-template."
New: pr-continuation-v1 template authored from the script that proved the pattern (see the brief I sketched in the conversation that prompted this RFC).
Open questions
Templating engine. Handlebars is the least-surprising default (well-understood {{var}} and {{#if}} semantics, zero learning curve). Alternatives: plain ${var} substitution (too limited for conditionals), Liquid (heavier), Tera/Jinja2 (heavier), eval (no). Handlebars unless someone has a strong objection.
Default references with conditionals. The example above uses "when": "reviewTaskId" to omit the reference when the param is absent. This is a tiny DSL; alternative is making defaultReferences an array of { ref, when?: (params) => boolean } evaluated at compile time. The DSL is more declarative but requires schema work; the function form is simpler but harder to serialize. Lean toward the DSL.
Success criteria inheritance. Can a template inherit a default successCriteria (rubric, gates, assertions)? Useful for assess-pr where the rubric is the point. Probably yes, with the same params substitution semantics applied to rubric content.
Discoverability for agents inside a task. When an agent inside a VM (running task A) wants to spawn a sub-task using a template, does it have access to the team's registry? Yes — via a new moltnet_list_task_templates / moltnet_task_from_template custom tool, mirroring the existing task-related tools. Out of scope for the initial RFC; mentioned for completeness.
Backend storage. New task_templates table is straightforward. The question is whether templates should also be content-addressable (a templateCid similar to inputCid) so that "task X was created from template Y@version Z" is auditable end-to-end. I'd say yes — it costs nothing and makes the chain queryable.
Acceptance criteria
A new task_templates REST resource (GET /task-templates, GET /task-templates/:id, POST /task-templates).
At least one global template shipped (bug-fix-from-issue-v1 or pr-continuation-v1) and one consumer-side migration (one of the tools/src/tasks/*.ts helpers converted).
Templating engine selected (default: Handlebars) and parameter-substitution semantics documented.
Backend schema validates template structure only — never template content.
Cross-references
Surfaced during the agent-runtime validation session that produced:
This RFC generalizes the lesson from #1131 (rubrics belong in the consumer's repo, not the library) to all opinionated content: templates belong in the consumer's team registry, not in the task-types lib.
RFC: Task templates
Motivation
Today the task abstraction is input/output schemas + a free-form
briefstring. The schemas constrain data shape; the brief carries all behavioral convention — what to do, which artifacts to consult, where to write back, when to stop, how to attribute work.Three failure modes show up once you run more than a handful of tasks:
The same workflow is re-authored every time.
tools/src/tasks/fulfill-brief.tshardcodes a 7-step PR workflow keyed off a GitHub issue. The continuation case ("apply review feedback to an existing PR") needs another helper with its own hardcoded workflow. The assess flow has a third. Each new pattern is another one-off helper, accumulating as tribal knowledge undertools/.The runtime instructor and brief overlap.
libs/pi-extension/src/runtime/runtime-instructor.tsalready says "usemoltnet_create_entry, sign every commit, useghwith App token." Any brief I write tends to repeat fragments of this. Two sources of truth that drift.The brief is the load-bearing element. Free-form text puts the interpretation burden on the model. Successful runs depend on careful prompt authoring every time; conventions are invisible until they're broken.
The conventions are minimal not because the system is permissive, but because the system has no place to put them yet.
Out of scope (rejected alternatives)
Encoding workflows in skills. Skills (
.claude/skills/,.agents/skills/) are operator-side advisory content bound to a human/agent identity. They aren't versioned with the task input — theinputCidis what audits a task, and a skill change wouldn't propagate to that. Wrong layer.Adding an
operationdiscriminator to task input schemas (fulfill_brief.operation: 'open_pr' | 'update_pr' | 'comment_on_pr'). Bakes opinionated workflow vocabulary into the backend. Explodes the schema, couples our task-types-lib to one team's conventions, makes the backend "a garbage bin" of named patterns.Splitting
fulfill_briefinto multiple task types per workflow. Same problem at a different layer. Task type proliferation that the daemon, executor, and registry all have to learn about.Proposal
Add a templates resource as a separate layer above the existing task types. Templates are data; they compile to a task input via simple substitution. The backend's job is storage + serving + per-team scoping; it has no opinion on template content.
Shape
{ "templateId": "pr-continuation-v1", "taskType": "fulfill_brief", "scope": "team:6743b4b1-..." | "global", "params": { "targetTaskId": { "type": "uuid", "required": true }, "reviewTaskId": { "type": "uuid", "required": false } }, "brief": "Continue PR linked from task {{targetTaskId}}. Read its accepted attempt for the PR url and branch. {{#if reviewTaskId}}Address concerns from review task {{reviewTaskId}}. {{/if}}Push to the existing branch; do NOT open a new PR. Sign each commit.", "defaultReferences": [ { "taskId": "{{targetTaskId}}", "role": "target_source" }, { "taskId": "{{reviewTaskId}}", "role": "judged_work", "when": "reviewTaskId" } ], "defaultSuccessCriteria": null }A template names a
taskTypeit compiles into, declares itsparams, and carries the brief text with placeholder substitution.Two-tier registry
@moltnet/tasksor a sibling lib. Examples:bug-fix-from-issue-v1,pr-continuation-v1. 5-10 total max — anything beyond that belongs in team registries.teamId. Backend has atask_templatestable keyed on(teamId | "global", templateId, version)with a publish endpoint. Like Postman collections — teams build their own playbook.SDK / CLI surface
task from-templateis the operator-side helper: load the template, validate the params against the declared shape, substitute into the brief, POST as the namedtaskType.Versioning
Templates are immutable per
(scope, templateId, version). Publishing a new version ofpr-continuation-v1ispr-continuation-v2. TheinputCidon a task pins the compiled brief, so revising a template doesn't retroactively change what prior tasks were judged against.What the backend validates
Only the template schema — that
paramsis well-typed,briefis a string,defaultReferencesmatchTaskRef's shape,taskTypeis registered. No semantic validation. The backend never knows that "update_pr" is a concept.What
tools/src/tasks/becomesDiscovery loop, as the user described it:
moltnet task from-template.The library never needs to know
pr-continuation-v1exists. The CLI'stask from-templateworks for any registered template authored by anyone.Migration
tools/src/tasks/fulfill-brief.ts→ its prompt becomesbug-fix-from-issue-v1template. The TS file shrinks to "fetch issue, populate params, callfrom-template."tools/src/tasks/assess-pr.ts→ already accepts an external rubric (per feat(tasks,tools): consumer-supplied rubrics for assess_brief + repo-specific merge gates #1131); the brief itself becomesassess-pr-with-rubric-v1template.pr-continuation-v1template authored from the script that proved the pattern (see the brief I sketched in the conversation that prompted this RFC).Open questions
Templating engine. Handlebars is the least-surprising default (well-understood
{{var}}and{{#if}}semantics, zero learning curve). Alternatives: plain${var}substitution (too limited for conditionals), Liquid (heavier), Tera/Jinja2 (heavier),eval(no). Handlebars unless someone has a strong objection.Default references with conditionals. The example above uses
"when": "reviewTaskId"to omit the reference when the param is absent. This is a tiny DSL; alternative is makingdefaultReferencesan array of{ ref, when?: (params) => boolean }evaluated at compile time. The DSL is more declarative but requires schema work; the function form is simpler but harder to serialize. Lean toward the DSL.Success criteria inheritance. Can a template inherit a default
successCriteria(rubric, gates, assertions)? Useful forassess-prwhere the rubric is the point. Probably yes, with the sameparamssubstitution semantics applied to rubric content.Discoverability for agents inside a task. When an agent inside a VM (running task A) wants to spawn a sub-task using a template, does it have access to the team's registry? Yes — via a new
moltnet_list_task_templates/moltnet_task_from_templatecustom tool, mirroring the existing task-related tools. Out of scope for the initial RFC; mentioned for completeness.Backend storage. New
task_templatestable is straightforward. The question is whether templates should also be content-addressable (atemplateCidsimilar toinputCid) so that "task X was created from template Y@version Z" is auditable end-to-end. I'd say yes — it costs nothing and makes the chain queryable.Acceptance criteria
task_templatesREST resource (GET /task-templates,GET /task-templates/:id,POST /task-templates).agent.taskTemplates.*.moltnet task templates …CLI subcommands (overlaps with feat(moltnet-cli):moltnet task attempts <id>subcommand #1130).moltnet task from-template <id> --param k=v ...CLI command.bug-fix-from-issue-v1orpr-continuation-v1) and one consumer-side migration (one of thetools/src/tasks/*.tshelpers converted).Cross-references
Surfaced during the agent-runtime validation session that produced:
fulfill_brief)assess_brief)moltnet task attempts <id>subcommand #1130 (CLI ergonomics for inspecting task output — adjacent concern)This RFC generalizes the lesson from #1131 (rubrics belong in the consumer's repo, not the library) to all opinionated content: templates belong in the consumer's team registry, not in the task-types lib.