Read a coworker from a file of its own, beside agents.yaml - #608
Merged
davidmckayv merged 4 commits intoSep 19, 2026
Merged
Conversation
agents.yaml holds every coworker a package ships, so adding one means editing a file somebody else is editing too, and handing somebody a coworker means handing them a fragment to paste into the middle of theirs. A package may now also keep a coworker per file in an agents/ directory beside agents.yaml. Both are read, and a package that keeps everything in agents.yaml loads exactly as it did. A file holds the coworker on its own, or a list under `agents:` for somebody splitting an existing file up. Only .yaml and .yml are read, so a README beside them is left alone. Row parsing moves into parseAgents, which takes the file it is reading so a refusal can name it: "agent.id is required" is no use when there are eleven files it could be in. Every check that applied to agents.yaml applies here, including the skill-slug check that fails a typo at boot rather than attaching nothing in silence. Two declarations of the same id stop the server and both files are named. Preferring one would make the roster depend on the order a directory listing came back in, and a clone that copied the same file in twice would never be told. The directory is in the package checksum for the reason skills.yaml is: a coworker added, edited or removed there is a package change, and a deployment that did not notice would go on running the roster it booted with. The new cases live in their own test file because tenant-package.test.ts opens a database at import and none of this touches one. Refs CopilotKit#397
Three coworkers is enough to prove the format and not enough to give anybody ideas, and writing a role_description cold is the part that decides whether a coworker answers usefully or vaguely. The first hurdle after docker compose up is not configuration, it is what now. Ten more ship in examples/fintech/agents/, one file each: reading an expense claim against the policy as written, turning a meeting note into the follow-ups actually in it, drafting release notes from what shipped, triaging a support ticket, answering a new starter from the handbook, writing a brief that names what it could not find, writing up an interview with question, answer and observation kept apart, handing an on-call shift over from the record, assembling what is known before a renewal decision, and grouping customer feedback into themes it can cite. They are deliberately unlike each other, because the point is that somebody recognises their own job in one. Each says what the job is, what the coworker must not do, and what to say when it cannot find something, because that is the part worth copying. They grant nothing. A coworker names skills, a skill names tools, and what it may call is what an administrator has granted, so a file dropped in here adds an instruction and no capability. Each names only skills this package already ships, and a clone deletes the ones it does not want. Ten rather than the twenty-eight written for CopilotKit#299: shipping all of them would put a directory nobody reads into every clone. The rest stay in awesome-openbot-agents, outside this repository and inherited by nobody. Refs CopilotKit#397
NathanTarbert
requested review from
MikeRyanDev,
davidmckayv,
guidovizoso,
mxmzb and
tylerslaton
as code owners
September 18, 2026 20:02
# Conflicts: # CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A package may now keep a coworker in a file of its own, in an
agents/directory besideagents.yaml. Both are read. A package that keeps everything inagents.yamlloads exactly as itdid, and nothing about it has to change.
This is @jerelvelarde's #397, and the framing there is what the change follows: the first hurdle
after
docker compose upis not configuration, it is what now. The loader was already most of theanswer, which is the part of that issue that made this small.
A file holds the coworker on its own, or a list under
agents:for somebody splitting an existingagents.yamlup — the second syntax is there so that split is a move rather than a puzzle. Only.yamland.ymlare read, so a README sitting beside them is left alone, and files are read infilename order rather than in whatever order the filesystem answers.
Row parsing moved into a
parseAgentsthat takes the file it is reading, so a refusal can name it."agent.id is required" is no help when there are eleven files it could have come from. Every check
that applied to a row in
agents.yamlapplies to a coworker arriving this way, including the onethat refuses a skill slug the package does not ship — a typo that silently attaches no skill is the
kind nobody finds, because the Bot just never narrows.
Two declarations of the same id stop the server, and the message names both files. Letting one win
would make the roster depend on the order a directory listing came back in, and a clone that copied
the same coworker in twice under two names would never be told. The directory is in the package
checksum for the reason
skills.yamlis: a coworker added or edited there is a package change, anda deployment that did not notice would go on running the roster it booted with.
On the question the issue left open — how many belong in the box — this ships ten, in
examples/fintech/agents/. Three proves the format and gives nobody ideas; twenty-eight is adirectory nobody reads and every clone inherits. Ten was a judgement call and I am happy to cut it
if you would rather: reading an expense claim against the policy as written, turning a meeting note
into the follow-ups actually in it, drafting release notes from what shipped, triaging a support
ticket, answering a new starter from the handbook, writing a brief that names what it could not
find, writing up an interview with question, answer and observation kept apart, handing an on-call
shift over from the record, assembling what is known before a renewal decision, and grouping
customer feedback into themes it can cite.
The prose in them is @jerelvelarde's, from the 28 written for #299, reshaped into the tenant
package's own format. They were worth reusing for exactly the reason that issue gives: each one is
specific enough that a reader recognises their own job in it, and the part worth copying is that a
role_descriptionsays what the job is, what the coworker must not do, and what to say when itcannot find something. The rest stay in
awesome-openbot-agents, outside this
repository and inherited by nobody.
None of them grant anything. A coworker names skills, a skill names tools, and what it may call is
what an administrator has granted, so a file dropped in here adds an instruction and no capability.
Each names only skills this package already ships, and a clone deletes the ones it does not want.
The new cases are in
server/tests/tenant-package-agent-files.test.tsrather than intenant-package.test.ts, because all of them are about parsing files and that suite opens adatabase at import.
Two things this deliberately does not do. Skills stay in one
skills.yaml, since the case there isweaker and nobody has asked for it. And a running deployment still cannot take a coworker in without
a redeploy — that is #398, and it is a different kind of change, because the file arrives from
outside.
Refs #397