An objective is one paragraph. Real work needs more: a refund policy, an escalation
matrix, worked examples, "how we word things." Today the only place to put that is
the objective string, which stops being editable prose somewhere around thirty
lines.
Shape
Markdown files composed into the agent's system prompt, declared in the versioned
config:
name: refund-triage
version: 1
objective: |
Resolve the refund request on ticket {{ inputs.ticket_id }}.
instructions:
- refund-policy.md
- escalation.md
Charter reads them at prompt-assembly time and appends them under labelled
sections, the same way add_context already stages history and memory.
Why static composition rather than a skills tool
Claude's SKILL.md model loads on demand — the agent decides what it needs and
reads it. That's the right design when there are hundreds of skills and a human is
steering. Charter's case is different, and on-demand loading has a specific problem
here: reading a file is a tool call, so it lands in the tool list, consumes
max_llm_calls, and needs governing. We already have a name for "the agent fetches
knowledge at runtime" — that's an MCP server, and the config for it exists.
So the split matches the one we drew for memory:
instructions: — what this agent always needs to know. Static, versioned,
composed into the prompt. Charter's job.
- an MCP server — a corpus too large to inline, fetched on demand. Not
Charter's job.
The hard part: immutability
Version files are immutable, and that's what makes set_version: 1 mean anything —
a rollback restores an agent that still exists on disk. Instruction files break
that: edit refund-policy.md without bumping the version and the rolled-back agent
behaves differently from the one that was rolled back to, silently.
Two candidate fixes:
- Version the directory, not the file.
agents/refund-triage/v1/agent.yaml
plus v1/refund-policy.md. Immutability is then a property of the directory and
the existing rule extends unchanged. Costs a layout change.
- Hash them at apply time, store the digest, and refuse to serve a version
whose instruction files no longer match. Keeps the layout; adds a failure mode
that fires long after the edit.
(1) is more in keeping with how the rest of this works, and it makes the versioned
artifact something you can look at.
Also worth deciding
- Context cost. Instructions ride on every LLM call of every round. Prompt
caching helps (the agent already sets cache=True), but charter validate should
report the token cost of the assembled prompt, the way it should for tool schemas.
- Templating.
{{ inputs.* }} inside an instruction file — same rules as the
objective, or not at all?
- Worker distribution. Instruction files are more files the worker needs on
disk, which makes the agents_dir coupling worse and strengthens the case for
storing the compiled config on the control plane.
Related
Long-running tasks also need context compaction — our _history grows every round
and is re-sent on every call, with full tool output in it. Instructions make that
budget tighter. Separate issue.
An objective is one paragraph. Real work needs more: a refund policy, an escalation
matrix, worked examples, "how we word things." Today the only place to put that is
the
objectivestring, which stops being editable prose somewhere around thirtylines.
Shape
Markdown files composed into the agent's system prompt, declared in the versioned
config:
Charter reads them at prompt-assembly time and appends them under labelled
sections, the same way
add_contextalready stages history and memory.Why static composition rather than a skills tool
Claude's SKILL.md model loads on demand — the agent decides what it needs and
reads it. That's the right design when there are hundreds of skills and a human is
steering. Charter's case is different, and on-demand loading has a specific problem
here: reading a file is a tool call, so it lands in the tool list, consumes
max_llm_calls, and needs governing. We already have a name for "the agent fetchesknowledge at runtime" — that's an MCP server, and the config for it exists.
So the split matches the one we drew for memory:
instructions:— what this agent always needs to know. Static, versioned,composed into the prompt. Charter's job.
Charter's job.
The hard part: immutability
Version files are immutable, and that's what makes
set_version: 1mean anything —a rollback restores an agent that still exists on disk. Instruction files break
that: edit
refund-policy.mdwithout bumping the version and the rolled-back agentbehaves differently from the one that was rolled back to, silently.
Two candidate fixes:
agents/refund-triage/v1/agent.yamlplus
v1/refund-policy.md. Immutability is then a property of the directory andthe existing rule extends unchanged. Costs a layout change.
whose instruction files no longer match. Keeps the layout; adds a failure mode
that fires long after the edit.
(1) is more in keeping with how the rest of this works, and it makes the versioned
artifact something you can look at.
Also worth deciding
caching helps (the agent already sets
cache=True), butcharter validateshouldreport the token cost of the assembled prompt, the way it should for tool schemas.
{{ inputs.* }}inside an instruction file — same rules as theobjective, or not at all?
disk, which makes the
agents_dircoupling worse and strengthens the case forstoring the compiled config on the control plane.
Related
Long-running tasks also need context compaction — our
_historygrows every roundand is re-sent on every call, with full tool output in it. Instructions make that
budget tighter. Separate issue.