Skip to content

Read a coworker from a file of its own, beside agents.yaml - #608

Merged
davidmckayv merged 4 commits into
CopilotKit:mainfrom
NathanTarbert:feat/coworker-per-file
Sep 19, 2026
Merged

davidmckayv merged 4 commits into
CopilotKit:mainfrom
NathanTarbert:feat/coworker-per-file

Conversation

@NathanTarbert

Copy link
Copy Markdown
Contributor

A package may now keep a coworker in a file of its own, in an agents/ directory beside
agents.yaml. Both are read. A package that keeps everything in agents.yaml loads exactly as it
did, 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 up is not configuration, it is what now. The loader was already most of the
answer, 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 existing
agents.yaml up — the second syntax is there so that split is a move rather than a puzzle. Only
.yaml and .yml are read, so a README sitting beside them is left alone, and files are read in
filename order rather than in whatever order the filesystem answers.

Row parsing moved into a parseAgents that 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.yaml applies to a coworker arriving this way, including the one
that 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.yaml is: a coworker added or edited there is a package change, and
a 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 a
directory 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_description says what the job is, what the coworker must not do, and what to say when it
cannot 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.ts rather than in
tenant-package.test.ts, because all of them are about parsing files and that suite opens a
database at import.

Two things this deliberately does not do. Skills stay in one skills.yaml, since the case there is
weaker 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

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 NathanTarbert changed the title Read a coworker from a file of its own, and offer ten to start from Read a coworker from a file of its own, beside agents.yaml Sep 18, 2026
@davidmckayv
davidmckayv merged commit 8279801 into CopilotKit:main Sep 19, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants