Skip to main content

Instructions and skills are templates over one documented context

Role instructions and skills could only say the same thing to every run, so guidance that depends on the situation (who asked, whether there is a repository, who a role reports to) had to be hardcoded into the opening prompt. We decided that they are Handlebars templates, rendered for each run against one context: the organization, the role and its place in the org chart, the roles, people (with their about), skills and projects, the task with its project, parent, creator, requester and assignee, or the conversation with its person and subscriptions, and where the agent works. The context is defined once, as a schema with a description on every field, in the template module; the reference people and agents read is generated from it, and run data is built to it in one place (run.opening.ts). That schema is the contract skills are written against, so it changes with care: fields are added, not renamed.

Considered Options​

  • Keep growing the hardcoded prompt: rejected; every organization's way of working would have to be Mission Control's, and nothing could depend on who asked.
  • A richer language (JavaScript, Liquid, Jinja-like): rejected; agents with skills.manage write templates too, and Handlebars runs no code of theirs and reads no object internals.
  • Lenient rendering, empty text for anything missing: rejected; a misspelled field would quietly vanish from a prompt. Templates render strictly instead, and a save is refused unless the template renders against sample data in which every field that may be empty is both empty and set, so a template that would fail for some run is caught when it is written.
  • Partials (one skill including another): left out for now; they raise questions of order, cycles and access (a disabled or unattached skill) without a need yet.

Consequences​

  • The role instruction and ambient skills render when a session starts; a triggered skill renders when the agent reads it with get_skill. A continued session keeps what it was given, as before.
  • What an agent saw is in its transcript: the rendered opening prompt and every get_skill result.
  • Editing needs the template as written: get_skill with template: true returns it, and the management tools take and return templates unrendered.
  • Helper arguments are all read before a helper runs, so and does not guard a field read from something empty; templates nest {{#if}} instead, and the reference says so.
  • A template that still fails at run start (data the samples did not foresee) fails the run with which template it was and why, rather than giving the agent a half-rendered prompt.
  • Templates see no secrets, environment variables or tokens.
  • A run works on a task or in a conversation (ADR 0011), so exactly one of task and conversation is set, and workspace is the conversation directory in a conversation. Making task optional is the one change that was not an addition: templates written while it was always set may read it unguarded. They keep rendering for tasks; the check on save renders against a conversation sample too, so such a template is caught when next edited. Until then a conversation with that role fails, saying which template and why in the conversation, rather than being given an instruction meant for a task. One instruction serves both, branching on conversation where they differ, rather than a role keeping a second instruction for conversations, which would repeat most of the first.

ADR 0034 adds agent conversations: neither task nor the human conversation is set there. Validation also renders against that context. Templates must guard each subject explicitly, using {{#if conversation}}…{{else if task}}…{{else}}…{{/if}} where they differ. A human conversation's else branch cannot assume a task. Existing customized instructions remain unchanged until their owner approves an edit; a failed live render names the incompatible template.