Skip to content

RFC: task templates — named, versioned, team-scoped brief patterns #1135

Description

@legreffier

RFC: Task templates

Motivation

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:

  1. 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/.

  2. 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.

  3. 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.

SDK / CLI surface

// SDK
await agent.taskTemplates.list({ teamId? });
await agent.taskTemplates.get(id);
await agent.taskTemplates.compile(id, params); // returns task input (no POST)
await agent.taskTemplates.publish({ templateId, taskType, params, brief, ... });
# CLI
moltnet task templates list
moltnet task templates get pr-continuation-v1
moltnet task templates publish ./my-template.json --team <id>
moltnet task from-template pr-continuation-v1 \
  --targetTaskId 10be8447-... --reviewTaskId d4bb5932-...

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:

  1. Author a one-off TS helper when a real need surfaces (current pattern — fine).
  2. Use it 2-3 times; let the prompt stabilize.
  3. Extract the prompt + param shape into a template JSON.
  4. Publish to the team registry.
  5. 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."
  • 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 becomes assess-pr-with-rubric-v1 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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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).
  • SDK methods on 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.
  • 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    rfcRequest for Comments

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions