Make a coworker by talking to one, and grant it nothing - #31
Merged
Conversation
Making a coworker means filling in a form, and the form asks for the one thing hardest to write cold: a role description that becomes standing instructions handed to a model on every turn in every channel. People type a sentence, get a coworker that answers vaguely, and never come back to fix it — the field that decides everything is the field a blank box is worst at. So it becomes an interview. The deployment ships a `bot-creator` skill whose instruction is how to ask: what job it does, what it must never conclude, and what it says when the evidence is thin. A Bot holding that skill is offered four tools — `list_bots`, `read_bot`, `list_bot_skills` and `save_bot` — and the interview ends in a card rather than in text somebody retypes into the form. The grant is the gate, not a flag. Four more tools on every run costs the per-run narrowing more than it buys, and a Bot for looking up transactions has no business making coworkers. The tools ride the signed-in session from the browser, so `POST /api/agents` answers who may and writes `bot.created` with the actor on it, unchanged. A server-side tool would have to carry an actor into runs that have none — a routine, a Slack thread, a schedule — and the first way that goes wrong is a coworker created under the wrong name. The card is the whole tool; there is no handler behind it. It shows the entire role description, unclipped, because that text will run on somebody's behalf and a clamp would hide half of what they are agreeing to. It can grant skills and nothing else: no address, no connector, no tool, no boundary, no visibility. A card that could grant capability would make asking a Bot the fastest route around the screens that exist to decide it. Two things found by using it. The first conversation answered "No skills exist here yet" to a deployment holding nine, because the handler read a query that had not loaded — an empty list and an unloaded list were sharing a code path, and the Bot repeated the tidier one to the person as fact. The answers now fetch when asked. And the link on a completed card lived in component state, so it would have appeared once and then silently stopped for anybody who reloaded; the coworker's id travels in the answer instead, read back by a function paired with the sentence. Verification: 321 pass, 0 fail across 48 files, format and lint clean. Driven end to end against a running deployment — a vague request, an interview, a card, a coworker, and a conversation with it that correctly says it cannot reach anything yet.
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 vague request, an interview that asks rather than assumes, a card carrying the whole instruction the
coworker will run on, and then a conversation with the thing that was just made — which correctly says
it can reach nothing. Full size, as video:
bot-creator.mp4
The problem
Making a coworker means filling in a form, and the form asks for the one thing hardest to write cold.
role_descriptionis not a label: with no endpoint it becomes the standing instruction handed to amodel on every turn in every channel that coworker is in. A blank box is the worst way to ask for it —
people type a sentence, get a coworker that answers vaguely, and never go back.
The three questions that actually make one useful are the ones nobody answers unprompted: what it must
never conclude, what it says when the evidence is thin, and whether something here already does the
job. A form cannot ask a follow-up. A conversation can.
The approach
A
bot-creatorskill, and the grant is the gate. The package ships it and grants it togeneral-assistant, besideskill-creator. A Bot holding it is offeredlist_bots,read_bot,list_bot_skillsandsave_bot; every other Bot is offered none of them, because four more tools onevery run costs the per-run narrowing more than it buys.
Browser-side, so nothing is re-implemented.
POST /api/agentsalready answers who may create acoworker and writes
bot.createdwith the actor on it;POST /api/plugins/grantsputs a skill on one.A tool riding the signed-in session inherits both unchanged. A server-side tool would have to carry an
actor into runs that have none — a routine, a Slack thread, a schedule — and the first way that goes
wrong is a coworker created under a name nobody chose.
The card is the whole tool.
save_bothas no handler: the run suspends, and the card shows thename, the job, the skills and the entire role description, scrolled rather than clamped. That text
runs on somebody's behalf, so a clamp would hide half of what they are agreeing to.
It grants skills and nothing else. No address, no connector, no tool, no boundary, no visibility.
Everything the coworker might reach is granted afterwards on its profile. A card that could grant
capability would make asking a Bot the fastest route around the screens that decide it.
Creating and granting are two calls and the pair is not atomic, so a grant that fails after the
coworker exists is named in the answer rather than rounded up — rolling the coworker back would delete
something the person just watched being made over a skill they can add in two clicks.
What is not covered
a coworker is a thing people have been talking to, and an unattended overwrite is not undoable.
coupling. The leg from interview to created coworker needs a model and a platform, so it is verified
by hand against a running deployment.
form.
bot.createdrecords the actor either way.Verification
format:check,lintandtypecheckclean. 377 pass, 0 fail across 57 files.server/tests/tenant-package.test.tspasses and was updated rather than left — it pins the shippedroster, and it is what caught the grant landing on Knowledge instead of the general assistant.
scripts/check-bot-templates.tsstill passes over all 27 shipped templates, now against 7 seeded slugs.Smoke-tested on this branch against a running deployment, with both authoring skills on one Bot: the
model called
list_botsandlist_bot_skills, drew the card, createdExpense Review, and granted it/check-a-claimand/find-a-document— both rows confirmed inplugin_grants, withbot.createdand two
configuration.changedin the audit trail.Two defects found by using it, both about state rather than logic. The first conversation this ever
had answered "No skills exist here yet" to a deployment holding nine — the model called the tool in the
first second of the run, the handler read a
useQuerythat had not returned, and an empty list and anunloaded list were sharing a code path. The answers now fetch when asked, from a module a test calls
against a cold query client. Second, the link on a completed card lived in component state, so it would
have drawn once and silently stopped for anybody who reloaded; the coworker's id travels in the answer
now, read back by a function paired with the sentence and pinned by a test.
app/tests/proposed-bot-card.test.tsxapp/tests/bot-tool-answers.test.tsapp/tests/bot-creator-slug.test.tsMerge notes
No migration, so the pre-deploy migration step does nothing and this cannot fail the way a schema
change can.
app/src/lib/copilot/provider.tsxgains one component besideSkillTools, andexamples/fintech/agents.yamlgainsbot-creatornext toskill-creatoron the general assistant —that line is the gate for the whole feature, and both
bot-creator-slug.test.tsand the tenant-packageassertion fail loudly if it is dropped.