Skip to content

feat: accept instruction as a list, let flavors append to it - #4144

Merged
dgageot merged 2 commits into
mainfrom
feat/instruction-list
Sep 3, 2026
Merged

feat: accept instruction as a list, let flavors append to it#4144
dgageot merged 2 commits into
mainfrom
feat/instruction-list

Conversation

@dgageot

@dgageot dgageot commented Sep 3, 2026

Copy link
Copy Markdown
Member

A flavor could only replace an agent's instruction wholesale: appending even a short block to a long system prompt meant duplicating the entire prompt inside the patch. The concrete case is docker/gordon's skills flavor, which appends a ~10-line <skills> block to a ~130-line base prompt.

What changes

instruction accepts a list of strings. The parts are joined by a blank line, like several instruction_file entries. Normalisation happens in Agents.UnmarshalYAML, which already round-trips each agent through a raw mapping, so AgentConfig.Instruction stays a plain string for every consumer, strict mode and error positions are unchanged, and HCL configs (converted to YAML first) get it for free. Marshalling emits the joined string: the list is input sugar only.

Flavor key+ promotes a scalar base to a one-element sequence before appending, so instruction+: [extra] extends a string instruction instead of erroring. A mapping base is still rejected. A nonsensical promotion (e.g. model+) surfaces as the usual type error at parse time, consistent with "a flavored config is validated like a hand-written one".

The "bump version" hint also fires on type errors. The list form only exists in the latest schema (15); documents pinned to 13 or 14 keep rejecting it, but now with hint: this syntax is supported by config version 15; update the top-level 'version' field (currently 13) instead of a bare cannot unmarshal []interface {} into ... string.

agents:
  root:
    instruction: You are a helpful assistant.

flavors:
  terse:
    agents:
      root:
        instruction+:
          - Answer in one sentence.

With --flavor terse the instruction becomes You are a helpful assistant.\n\nAnswer in one sentence.

Also updates agent-schema.json, the agents/flavors/overview docs, and adds a terse flavor to examples/flavors.yaml.

Validation

  • go test ./pkg/config/... ./pkg/teamloader/... ./pkg/oci/... (includes schema-vs-examples and doc-YAML tests)
  • golangci-lint run, go run ./lint .
  • docker agent debug config examples/flavors.yaml --flavor terse, and on gordon.yaml: the skills flavor rewritten with instruction+ resolves to the same config as the standalone flavor file.

@dgageot
dgageot requested a review from a team as a code owner September 3, 2026 13:45
@aheritier aheritier added area/config For configuration parsing, YAML, environment variables kind/feat PR adds a new feature (maps to feat:). Use on PRs only. area/docs Documentation changes status/needs-signed-commits Some commits in the PR are signed with a valid SSH/GPG key labels Sep 3, 2026
@aheritier

Copy link
Copy Markdown
Collaborator

👋 Some commits in this PR are not signed and verified by GitHub. Please sign your commits with a GPG or SSH key registered in your GitHub account, then force-push.

Commits that are not verified: 36ecf28, 5d91038

See GitHub's guide on signing commits for setup instructions. I've added status/needs-signed-commits; it will be removed automatically once every commit in this PR carries a valid GitHub-verified signature.

… to it

An agent's `instruction` was a plain string, so the only way for a flavor
to add to the system prompt was to replace it wholesale — duplicating the
entire prompt in the patch and defeating the point of flavors. Gordon's
`skills` flavor hit exactly this: a ~10-line `<skills>` block that should
be appended to a ~130-line base prompt.

Let `instruction` be written as a list of strings, joined by a blank line
like several `instruction_file` entries are. The normalisation happens in
Agents.UnmarshalYAML, which already round-trips each agent through a raw
mapping, so AgentConfig.Instruction stays a string for every consumer,
strict-mode and error positions are unchanged, and HCL configs (converted
to YAML first) get it for free. Marshalling emits the joined string: the
list is input sugar only.

In flavor patches, `key+` now promotes a scalar base to a one-element
sequence before appending, so `instruction+: [extra]` extends a string
instruction instead of erroring. A mapping base is still rejected. Bad
promotions (e.g. `model+`) surface as the usual type error at parse time,
consistent with "a flavored config is validated like a hand-written one".

Signed-off-by: David Gageot <david.gageot@docker.com>
…n keys

The "bump the top-level version" hint only fired for unknown fields. New
syntax that changes a value's shape — `instruction` as a list, or a flavor
`instruction+` on a version-13 document — failed with a bare "cannot
unmarshal []interface {} into ... string" instead. The existing probe
("does a newer schema parse this document?") already answers that case,
so match yaml.TypeError as well and word the hint around syntax rather
than keys.

Signed-off-by: David Gageot <david.gageot@docker.com>
@dgageot
dgageot force-pushed the feat/instruction-list branch from 5d91038 to 76c8452 Compare September 3, 2026 14:17
@aheritier aheritier removed the status/needs-signed-commits Some commits in the PR are signed with a valid SSH/GPG key label Sep 3, 2026
@dgageot
dgageot merged commit defca74 into main Sep 3, 2026
19 checks passed
@dgageot
dgageot deleted the feat/instruction-list branch September 3, 2026 15:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config For configuration parsing, YAML, environment variables area/docs Documentation changes kind/feat PR adds a new feature (maps to feat:). Use on PRs only.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants