Lab 16: the Brightline AP plugin v0.1
- Approved by
- Panaversity ·
Time: about 8 hours 15 minutes, in three sittings, plus an optional hour at work for Task 21. The main route is about 6 hours 40 minutes. Lab files: brightline-lab-ch16.zip, standalone. The same steps are in LAB.md inside the zip.
Before you start
What you need.
- The Claude desktop app with a Pro, Max, Ultra, Team or Enterprise plan, for Sittings 1 and 2.
- Git, Node.js 24 or later, and uv, the Python project tool. The connector in Task 9 asks for Node.js 24.
python3on your path, because the skill's script and one test call it. On Windows, ifpython3opens the Microsoft Store or is missing, install Python from python.org, or ask the agent to useuv run pythonin its place.- The
claudecommand, which the desktop app does not include. Install it from Anthropic's quickstart. The agent runsclaude plugincommands with it, and a session started with it can load a plugin folder for that session alone, which is how you test the plugin while you build it. - A GitHub account, and the GitHub command-line tool
gh, from cli.github.com. Task 10 signs you in. - For Tasks 18 and 19, the ChatGPT desktop app with Codex, and the Codex command-line tool for the
codex plugin marketplacecommands. - For Task 20, OpenCode version 2, with a model connected. Install it with
npm install -g @opencode/cli. The olderopencode-aipackage is version 1. - For Task 17, a Claude plan that can install plugins on claude.ai: Pro, Max, Ultra, Team or Enterprise. For Task 19, a ChatGPT plan that can install plugins: OpenAI's plan table shows Plus, Pro, Business and Enterprise/Education. Both tasks are optional. If you skip one, write "skipped" and why in
ports/ch16.md.
Set up the tools. If one is missing, follow Concept 13.2 before Task 1. It gives the official page for each, and a prompt that has the agent check the versions and install nothing.
You need no earlier chapter or lab. The zip holds a complete, working repository. Where a task uses an idea from an earlier chapter, it explains it in place.
The main route and the extras. The main route is Tasks 1 to 11, then Tasks 14 to 16, then one port, Task 18 or Task 20, and the last four steps of Task 20: the port notes, the handover notes, the tag and the clean-up. It builds, releases and ports the plugin. Tasks 12, 13, 17 and 19, the second port, and Task 21 deepen what you built, so come back to them when you can.
For a reader without a paid Claude plan, see the section near the end of this page.
The lab files. Download brightline-lab-ch16.zip and unzip it into a new folder. It contains:
brightline-ap/, a small, working accounts payable toolkit for a made-up company, with no git history. In Task 1, the agent puts it under git. ItsREADME.mdsays what each folder holds. It also holds the lab's starting files:docs/ap-triage-rules.md, the six triage rules, and the cases the AP analyst checks by handdata/invoices-intake.csv, a new batch of 13 invoicesschemas/agent-plugins/1.0.0/, the Agent Plugins JSON Schemas, copied from the standard's repositoryexercises/ap-helper-plugin/, a plugin a colleague forwarded. Never install or run it. Task 14 asks you to read it.
LAB.md, these steps, with its figure inimg/.
Each task links its answer key as a separate download. Open a key in a text editor, never in the agent. An agent that can read the answers is not tested by them.
Use the zip's copy for this lab, even if you finished an earlier lab in your own repository. The answer keys match the zip's files exactly.
The commands, menus and settings for each product are in 16.10, and each task links the part it uses.
The date in this lab. Today is Monday, March 8, 2027. Triage uses that run date, and "this Friday" is March 12, 2027.
The job
Ana Morales, Brightline's AP analyst, wants the Monday triage done the same way by everyone, in whatever AI tool they use, with the numbers from tested code and one reviewed set of rules. She wrote the rules. You direct coding agents to turn them into tests, an engine, three skills and a plugin, ship it through review, and put the same skills in six places: Claude Code, a Claude chat and a Claude task, Codex and ChatGPT, and OpenCode. Where a Claude account has chat and tasks in one conversation, that is five places. You also read a plugin a colleague forwarded, before anyone installs it. Then, in Task 21, you use the same steps on a job from your own work.
How each task works
Each task has a goal, steps and a Checkpoint. Do not start the next task until the checkpoint is true. In the Claude desktop app's Code tab, work in Manual mode, where the agent asks before each edit and each command.
The agent runs every command in this lab, and you read what it prints. Three things are yours, and the tasks say when: you sign in to GitHub yourself, you click through GitHub's settings page, and you start a test session from a terminal with the claude command. For that, the Code tab has its own terminal: click Terminal in the session's title bar. Before you approve a command, read what the agent says it will do, and say no to anything you do not understand. Asking "What does this command do?" is always fine.
For each task:
- Predict. Write in
NOTES.mdwhat should happen, under the task's number. - Direct. Send the prompt. When the agent proposes a plan, read it before you let it write.
- Review. Read every change and every command before you approve it. Ask the agent to explain anything you cannot follow.
- Check. Ask the agent to run the checks, read what they print, and compare it with your prediction.
The four checks are npm test, npm run typecheck, npm run test:py and npm run check:py. Every number in your notes comes from a command's output. Keep NOTES.md and ports/ch16.md out of task commits until Task 20.
The prompts in quote boxes are starting points. Send each one as written the first time, then correct the agent in your own words when it goes wrong. Those corrections are what this lab teaches, so note the important ones in NOTES.md.

Figure 16.16. The agent writes. You predict, direct, review and check, in every task.
Sitting 1: build it on Claude (about 3 hours 30 minutes)
Read Concepts 16.1 to 16.5 first.
Task 1. Set up, and your first skill (25 minutes)
Goal: the repository working on your computer, an agent that knows it, and your first skill answering a real question.
-
Open
brightline-apin the Code tab, in a Local session, in Manual mode. -
Send:
Please put this folder under git, with a main branch, and make a first commit of everything here, with the message "Start from the lab's files". Then show me
git log --oneline.Expect
git init -b main,git add .,git commitandgit log --oneline, which shows one commit. If the agent wants to change your git settings for the whole computer, say no. Ask it to set the name and email for this project only. -
Predict first: all four checks pass, with 42 TypeScript tests and 27 Python tests. Then send: "Please install the packages from the lock files, with
npm ciand, inpy/,uv sync. Then runnpm test,npm run typecheck,npm run test:pyandnpm run check:py, and show me what each one prints." -
Send:
Please read
README.md,AGENTS.mdandREVIEW.md, and list the files insrc/,tests/,data/and.claude/skills/. Explain in about ten lines what this repository does, what its four checks are, which rules every agent here must follow, and what thedue-by-fridayskill does. Change nothing.Check its answer against
README.md.AGENTS.mdis the repository's standing instructions to every coding agent, and Claude Code reads it throughCLAUDE.md. -
Your first skill. Predict first: the agent will ask to run
npm run due -- 2027-03-12, thennpm test, and report 13 invoices, $43,833.63, with every test passing. These are open invoices in the AP export, a different 13 from the new batch you meet in Task 3. Then, in the same session, type/due-by-friday 2027-03-12and send it. Approve the commands, then compare its reply with what the commands printed. That is a skill at work: a short instruction file you started by name, and a number that came from tested code, not from the model. By Task 8 you will have three skills packaged as a plugin.
Checkpoint: four checks pass, the agent's summary matches README.md, and the skill's answer, 13 invoices, $43,833.63, matches the command's output.
Check it now. Download Task 1's answer key.
Task 2. Which piece? (15 minutes)
Goal: place six AP jobs before any tool is open (16.2).
In NOTES.md, under "Task 2", write the piece for each job, and one reason:
- Read the new batch file.
- Work out which invoices are due within three days of the run date.
- Write the Monday report in the shape the AP team reads.
- Check a vendor's tax form in the purchasing system.
- Never change a vendor's terms in
data/vendors.csv. - Read two years of old statements to find one vendor's past disputes, without crowding this session.
Checkpoint: your pieces match the answer key's table, or you can say why yours would also be reliable.
Check it now. Download Task 2's answer key.
Task 3. Rules into tests (40 minutes)
Goal: Ana's rules as failing tests, written before any code (16.3). Tests written first, from the expert's sheet, are the independent answer the code must match.
-
Read
docs/ap-triage-rules.mdyourself, whole. Note the order of the rules and the two boundaries. -
Send: "Please make a new branch called
triageand switch to it." Then send:Please read
docs/ap-triage-rules.md. Writetests/triage.test.tsfor a functiontriage(invoice, vendors, runDate, seen)insrc/triage.ts, using theInvoiceandVendortypes fromsrc/schemas.ts, whereseenholds the AP export (ledger) and the invoicesearlierin the same batch. It returns{ pile, reason?, due }, with pile one of "exception", "needs approval", "urgent", "ready". Write one test per rule and one per case in the sheet's table, with run date 2027-03-08. Add a test for a functiontriageBatch(batch, vendors, ledger, runDate)that returnscountsandtotalsin cents per pile, but do not fill in the expected numbers for the intake batch. Take every expected value from the rules sheet only. Do not writesrc/triage.ts. Show me the tests before you write them. -
Work out the batch test's expected numbers yourself. This is the expert's check: you sort and add yourself, so the engine has an independent answer to match. Allow twenty minutes. First send:
Please print a table of the 13 rows of
data/invoices-intake.csv, in file order: the invoice number, the vendor id, the invoice's terms, that vendor's terms indata/vendors.csv, whether the number is already indata/invoices.csv, the amount in cents, and the due date. Where the due date is blank, get it fromdueDateinsrc/due-date.ts. Do not decide any pile.Then apply the six rules to each row, in order, in a table in
NOTES.md, and add up each pile's cents with a calculator. Send the agent your counts and totals, and ask it to put them in the batch test. -
Predict first: every new test fails, and only because
src/triage.tsdoes not exist. Ask the agent to runnpm test. -
Ask the agent to commit the tests on
triage.
Checkpoint: the tests are committed while failing for the right reason, and your batch numbers match the answer key's.
Check it now. Download Task 3's answer key.
Task 4. The engine (20 minutes)
Goal: a tested engine that prints every number.
-
Send:
Please write
src/triage.tsso the tests intests/triage.test.tspass. UsedueDatefromsrc/due-date.tsfor a blank due date. Put the $5,000.00 limit and the three days in named constants,CONTROLLER_LIMIT_CENTSandURGENT_DAYS. Addnpm run triage -- <batch.csv> <run date>that prints each pile with its count and total and each invoice with its reason, and ends with a line in the formN invoices: N exception, N needs approval, N urgent, N ready. Give it a--jsonoption. Do not change any test. -
Ask the agent to run
git diff HEAD -- tests. It must print nothing, because no test changed. -
Ask the agent to run the four checks, then
npm run triage -- data/invoices-intake.csv 2027-03-08. Then ask it to commit.
Checkpoint: every check passes, and the last line is 13 invoices: 4 exception, 2 needs approval, 2 urgent, 5 ready. If your Task 3 numbers differed, find which rule you applied differently.
Check it now. Download Task 4's answer key.
Task 5. The skill (30 minutes)
Goal: invoice-triage, to the Agent Skills specification (16.1, 16.3).
-
Send: "Please create
plugin/skills/invoice-triage/, and copydocs/ap-triage-rules.mdinto itsreferences/folder, unchanged." -
Send:
Please write
plugin/skills/invoice-triage/scripts/triage.py, a standard-library-only Python copy ofsrc/triage.ts, with--intake,--vendors,--ledger,--run-dateand--json, for products that load a skill but have no repository. Then addtests/triage-parity.test.ts, which runs it with--jsonon the intake batch and checks that its counts, totals and piles equaltriageBatch's. Runnpm test. -
Have the agent draft
SKILL.mdwith you, playing Ana:Please draft
plugin/skills/invoice-triage/SKILL.mdto the Agent Skills specification, using only its six fields, withmetadataholdingauthor,version: "0.1.0"andpolicy: "AP policy v3". Before you write, ask me up to five questions, one at a time, as if I were Ana, the AP analyst: when people need this, when they must not use it, and who acts on each pile. Then writename: invoice-triage, adescriptionof at most 1,024 characters that says what it does, when to use it and when not to, and a body that finds the three files, asks for the run date if it is missing, runs the tested tool (npm run triageinside the repository, orscripts/triage.pyanywhere else), copies its numbers exactly, ends with who acts on each pile, and has a "Never" list. Point toreferences/ap-triage-rules.mdrather than copying the rules into the body.Answer its questions from
docs/ap-triage-rules.md. Then read the draft line by line and change anything Ana would not sign. You decide every line. The agent only types it. -
Ask the agent to check the skill against the specification and show each rule with pass or fail: the six fields, the name and its folder, the description's length, and fewer than 500 lines. Check one or two of its answers yourself. Task 15 makes this a required check.
-
Send: "Please add
py/brightline_ap/triage.py, a Python version of the engine usingdue_date, andpy/tests/test_triage.pywith the same rules and the batch's counts and totals. Runnpm run test:pyandnpm run check:py." -
A report is useful later only if it says which copy made it. Send:
Please make both tools read
metadata.versionandmetadata.policyfrom the skill'sSKILL.md, and print them on the first line, such asTriage for run date 2027-03-08, brightline-ap 0.1.0, AP policy v3.src/triage.tsreadsplugin/skills/invoice-triage/SKILL.md, andscripts/triage.pyreads theSKILL.mdin its own skill folder, so it still works when the skill is copied. Add them aslabelsin the--jsonoutput, and make the parity test compare them. Runnpm test. -
Nothing yet notices if the sheet or one copy of its rule moves alone. Send:
Please add
tests/triage-rules-sheet.test.ts, which fails unless every dollar amount in rule 4 ofdocs/ap-triage-rules.mdequalsCONTROLLER_LIMIT_CENTSin each ofsrc/triage.ts, the skill'sscripts/triage.pyandpy/brightline_ap/triage.py, and unless the policy on the sheet's source line equals the skill'smetadata.policy. Runnpm test.Then ask the agent to commit.
Checkpoint: the parity test, which checks that the two copies agree, and the sheet test both pass, the skill uses only the six fields, every number it mentions comes from a script it names, and the tool's first line names brightline-ap 0.1.0 and AP policy v3.
Check it now. Download Task 5's answer key.
Task 6. Does it load? (15 minutes)
Goal: the description loads the skill for the right requests and not others.
- In the session's terminal, in the
brightline-apfolder, start a new session with the skill folder loaded for this session only:claude --permission-mode manual --plugin-dir ./plugin. Without the first flag, a terminal session starts in Auto mode. A folder of skills already loads, under the folder's name,plugin. Task 8 adds the manifest. - Send five requests, one at a time, and record whether
invoice-triageloaded. Type/clearbetween requests, so one answer cannot lean on the last:- "Triage the new batch in data/invoices-intake.csv for today, March 8, 2027."
- "Which of this week's new invoices need someone to look at them?"
- "Sort data/invoices-intake.csv into the AP piles."
- "What is the controller's approval limit?"
- "Should we pay INV-6004 today?"
- The first three should load it. The last two should not. If one misbehaves, change the description, not the steps, type
/exit, start the session again, and try again. Run each request twice, because models vary.
Checkpoint: a table in NOTES.md with five rows and two runs each, all as expected, and the description you ended with.
Check it now. Download Task 6's answer key.
Task 7. The command (15 minutes)
Goal: the Monday report started by name, and the due list moved into the plugin (16.4).
-
Back in the Code tab, read
.claude/skills/due-by-friday/SKILL.md, the skill you ran in Task 1. Noticedisable-model-invocation: true. Claude Code reads that field, but it is outside the standard's six, so a strict product would refuse this file. Send:Please move
.claude/skills/due-by-friday/toplugin/skills/due-by-friday/withgit mv. Keep only the Agent Skills specification's six fields, sodisable-model-invocationgoes. Write adescriptionthat says what it does and when to use it. The cutoff date comes after the skill's name, such as/due-by-friday 2027-03-12. If none is given, it asks for one, and it never works out a date itself, not even "this Friday". It runsnpm run due, and reports the script's numbers exactly. -
Send:
Please write
plugin/skills/ap-triage/SKILL.md, the Monday report, with only the six specification fields. It takes a batch file and a run date after its name, asks for either if missing, followsinvoice-triage, and writes the report in a fixed shape with every number copied from the tool, then stops. The report's first line is one line, such asAP triage, run date 2027-03-08, batch invoices-intake.csv, brightline-ap 0.1.0, AP policy v3, with the plugin version and the policy as the tool printed them. It only reads, so it may load by itself too.Read both skills line by line before you accept them. Then ask the agent to commit.
-
You run them in Task 8, once the manifest gives the plugin its name. Without a manifest, the plugin would take its folder's name,
plugin, which is easy to confuse with the built-in/plugincommand.
Checkpoint: both skills meet the specification, with six fields at most, and each name matches its folder. Neither skill works out a date or a number itself.
Check it now. Download Task 7's answer key.
Task 8. The plugin (25 minutes)
Goal: two manifests around one skills/ folder (16.5). One host reads the standard core, and another reads only its own manifest, so both sit beside the same skills. You will see why in Sitting 3, with 16.7.
- Ask the agent for
plugin/plugin.json, the Agent Plugins core:$schemafor 1.0.0,name: brightline-ap,version: 0.1.0, a description, an author, a license and keywords. Nothing else. - Ask for
plugin/.claude-plugin/plugin.json, Claude's own manifest: the same name, version and description. Claude Code does not read the core yet, so this file sits beside it. Both point at the sameskills/folder by convention. Nothing is copied. - Ask the agent to run
claude plugin validate ./plugin, then to validateplugin/plugin.jsonagainstschemas/agent-plugins/1.0.0/plugin.schema.json. Then ask it to commit. - In the terminal, start a new session with
claude --permission-mode manual --plugin-dir ./plugin. Run/brightline-ap:due-by-fridaywith no date, and see it ask for one. Then run/brightline-ap:due-by-friday 2027-03-12, then/brightline-ap:ap-triage data/invoices-intake.csv 2027-03-08.
Checkpoint: both checks pass, both skills appear under brightline-ap, the due list for March 12 is 13 invoices, $43,833.63, as npm run due -- 2027-03-12 prints, and the report's counts match Task 4's output.
Check it now. Download Task 8's answer key.
Task 9. A ready-made connector (25 minutes)
Goal: a pinned, read-only connector to the data folder, with no code (16.5).
- Ask the agent for the connector in both formats, then ask it to commit:
plugin/mcp.json(the core): astdioserver namedapdatathat runsnpx -y @modelcontextprotocol/server-filesystem@2.1.0 ./data, with the same$schemaversion asplugin.json.plugin/.mcp.json(Claude's file): the same server, with the folder taken from auserConfigoption in.claude-plugin/plugin.jsonnameddata_folder, of typedirectory, with a title, a description and the default./data.
- Read the server's tool list in 16.10. Four tools write:
write_file,edit_file,create_directoryandmove_file. The plugin cannot remove them. Ask the agent to add four deny rules to.claude/settings.json, using the names in 16.10, such asmcp__plugin_brightline-ap_apdata__write_file. Then ask it to commit. - In the terminal, at the root of
brightline-ap, start a new session withclaude --permission-mode manual --plugin-dir ./plugin. Nothing asks for the data folder here, because this kind of session shows no dialog, so the default./dataapplies, counted from the folder you started in. Ask: "Using the apdata connector only, list the folders it may read, then list the files in that folder." - Predict first, then ask: "Using the apdata connector, write a file named test.txt in the data folder." A deny rule that names a tool removes it from what the agent can see, so the agent should say it has no such tool. Its refusal alone proves nothing, because
AGENTS.mdalso forbids the write. So ask: "Please list the apdata connector's tools." Then look indata/yourself. - Keep the connector read-only everywhere. The connector can write files, and the plugin cannot stop that. Each product's settings can. Before you use the plugin in any new place, deny or block its four write tools there first. The tasks remind you each time.
Checkpoint: the connector lists exactly one allowed folder, your repository's data/, and its files. The agent lists ten tools, with none of the four write tools, and data/ holds no test.txt. The /mcp screen still shows all fourteen, because it lists what the server offers. If the allowed folder is wrong, you started the session somewhere else. Exit, and start it again from the repository's root.
Check it now. Download Task 9's answer key.
Sitting 2: ship it (about 3 hours 5 minutes)
Read Concepts 16.6, 16.8 and 16.9 first.
From here on, changes reach main only through a pull request that passes the checks. Task 15 opens triage as one pull request, which keeps the first build in one review. From Task 17 on, each task that changes files is its own pull request, with the template filled in, the checks green, and the merge. Ask the agent to stage only the files each task needs.
Task 10. Put it on GitHub (25 minutes)
Goal: the repository on GitHub, with its three checks running and main protected, before you ship anything.
-
Ask the agent to run
git status. Ontriage, onlyNOTES.mdmay show as changed. Everything else is committed. -
Sign in yourself. In the terminal, never through the agent's chat, type
gh auth login, and answer its questions. Check withgh auth status. -
Send: "Please switch to
main, create a public GitHub repository from this folder withgh repo create brightline-ap --public --source . --remote origin, and pushmainwithgit push -u origin main. This first push createsmainon GitHub, so the rule against pushing tomainstarts after it. Do not pushtriageyet."--publicis for this lab only: on a free account, rulesets, branch protection and code owners work only on public repositories,1 and everything in Brightline is made up. -
Make your first pull request. Send: "Please make a branch called
setupfrommain, and replace@your-github-namein.github/CODEOWNERSwith@<your GitHub name>. Commit it, push the branch, and open a pull request withgh pr create --fill." GitHub reads that file from the pull request's base branch, and the last matching line wins.2 The three checks,checks,tests-unchangedandno-secrets, run on it. Wait until they are green. -
Protect
main, yourself, on GitHub. Open the repository's Settings, then Rulesets under Code, planning, and automation (some pages still say Rules), then New ruleset, New branch ruleset. Name itmain, set Enforcement status to Active, and target the default branch. Turn on:- Restrict deletions and Block force pushes
- Require a pull request before merging, with 1 approval, dismiss stale approvals, require review from Code Owners, and require conversation resolution
- Require status checks to pass before merging. Type each check's name to add it:
checks,tests-unchangedandno-secrets. A required check must have succeeded in the repository in the past seven days, so add them after they have run on your pull request.
GitHub does not let you approve your own pull request. If you work alone, add Repository admin to the bypass list, with its mode set to For pull requests only, and write why in the pull request each time you bypass.
-
Merge the pull request on GitHub.
-
Send: "Please run
git switch main,git pull,git switch triageandgit merge main. Then run the four checks."
Checkpoint: the three checks are green on GitHub, main accepts changes only through a pull request, and triage has your code owner line and still passes every check.
Check it now. Download Task 10's answer key.
Task 11. Version and catalog (15 minutes)
Goal: version 0.1.0, a changelog, and a catalog to install from (16.6).
-
Ask the agent for
plugin/CHANGELOG.mdwith an entry for 0.1.0, dated March 8, 2027: what was added, including the connector's pinned version and where its write tools are denied. -
Ask for
.claude-plugin/marketplace.jsonat the repository root: a catalog namedbrightlinewith one plugin,brightline-ap, from./plugin, with noversionin the entry.plugin.jsonholds the version, and Anthropic says not to set it in both. -
Send: "Please commit, push the branch, and clone it into a new folder next to this one, called
ch16-install. From that clone's root, runnpm ci, thenclaude plugin marketplace add ./ --scope local, thenclaude plugin install brightline-ap@brightline --scope local." The agent may ask before it runs commands in that folder. It is your own clone, so allow them. Local scope installs it for you, in that clone only. The second command says one option is not yet set. The default applies, so go on. -
Open a new Code tab session in the clone's root, so its
.claude/settings.jsondenies the connector's write tools and the data folder is the clone's. Run/brightline-ap:ap-triage data/invoices-intake.csv 2027-03-08. Never use the plugin in a folder whose settings do not deny the write tools. -
Back in your first session, send: "From
ch16-install, please runclaude plugin uninstall brightline-ap@brightline --scope local, thenclaude plugin marketplace remove brightline." Later tasks use the folder.
Checkpoint: the installed plugin worked in a new session, with the same counts, and claude plugin list no longer shows it.
Check it now. Download Task 11's answer key.
Task 12. Repeatable checks (15 minutes)
Goal: four test cases you can run before every release (16.6).
- Ask the agent for
evals/README.md, at the repository root and not insideplugin/, with four cases: the intake batch for March 8, 2027, the same batch for March 11, a batch with a missing run date, and the Monday report's first line. Predict the first two with the rules, then check them withnpm run triage. The third should end in a question, not a guess. The fourth should namebrightline-ap 0.1.0andAP policy v3. - Run the cases by hand in a new session, twice each. If you would rather have a machine run them,
claude plugin eval init <name>, run fromplugin/, writes a suite underplugin/evals/, so those cases ship inside the plugin. It needs Claude Code 2.1.269 or later, and its runs count toward your plan or API bill. - Record the results in
NOTES.md, and ask the agent to commitevals/README.md.
Checkpoint: four cases and their results, and a sentence on which result surprised you, if any.
Check it now. Download Task 12's answer key.
Task 13. Watch it run (25 minutes)
Goal: see a plugin in telemetry, and what telemetry cannot show (16.8).
-
Predict first: your plugin came from your own folder, not Anthropic's catalog, so
plugin.nameandskill.nameare hidden. -
Send:
Please run
claude -p "/brightline-ap:ap-triage data/invoices-intake.csv 2027-03-08" --plugin-dir ./plugin --allowedTools "Bash(npm run triage *)", and save everything it prints toout/telemetry-1.txt. SetCLAUDE_CODE_ENABLE_TELEMETRY=1,OTEL_METRICS_EXPORTER=consoleandOTEL_LOGS_EXPORTER=consolefor this one command only, and put them in no settings file. Then search the file forplugin_loaded,skill_activated,cost.usageandtoken.usage, and show me each record'splugin.nameandskill.name, the run's cost, and how long each model request took.claude -pruns one request with no conversation. Its console output lands in the file, which a chat session would hide. The file holds your email address and account ids.out/is ignored by git, so it stays out of the repository. Do not share it. -
Send the same request again with
OTEL_LOG_TOOL_DETAILS=1added, saving toout/telemetry-2.txt, and find the real names. This setting also logs commands and tool input, so use it only on made-up data like this. -
Turn on nothing else. Never log prompts or replies in this lab, even though the data is made up.
-
In
NOTES.md, write what telemetry showed (which events, how long, what it cost) and what only the tests from Task 3 showed.
Checkpoint: the names hidden, then real, and a note that names the test, not the telemetry, as the evidence that the counts are right.
Check it now. Download Task 13's answer key.
Task 14. Read before you install (15 minutes)
Goal: decide about a stranger's plugin by reading it (16.9).
- Do not install, load or run anything in
exercises/ap-helper-plugin/. Read each file yourself, or ask the agent to read them and change nothing. - Fill in the table from 16.9 for this plugin: the manifest, the hooks, the scripts, any connectors, the skill.
- Write your decision in
NOTES.md, with the file and line that decided it, and the address data would go to.
Checkpoint: a written decision, with the file, the line and the address behind it.
Check it now. Download Task 14's answer key.
Task 15. Open the pull request (60 minutes)
Goal: the plugin on main through the review setup from Task 10, with the plugin checks required.
-
Ask the agent to add
jsonschemaandpyyaml, pinned, to thedevgroup inpy/pyproject.toml, then to runuv lockanduv syncinpy/. The checks on GitHub install with--locked, so a missing lock entry would fail the job. -
Send:
Please write
scripts/check_plugin.py, run withnpm run check:pluginthroughpy/'s environment. It must exit 2, not pass, if it cannot find the folders. It must fail when:plugin/plugin.jsonorplugin/mcp.jsonbreaks the Agent Plugins 1.0.0 schemas inschemas/agent-plugins/1.0.0/, ormcp.jsonnames a different specification version fromplugin.json- a skill in
plugin/skills/uses a field outside the six specification fields, breaks the name rules, has a name that differs from its folder, has a description outside 1 to 1,024 characters, or has aSKILL.mdof 500 lines or more - a skill's
metadata.versiondiffers from the manifest's, the Claude manifest's name or version differs from the core's, orCHANGELOG.mdhas no entry for the version - a rules sheet in a skill's
references/differs from its copy indocs/
Add tests in
py/tests/that copy the plugin and break each rule once.You do not need to follow every line of the script. Review what it promises: ask the agent to show each rule beside the test that breaks it. The limit and the policy are not on this list, because Task 5's sheet test already compares them, and the
checksjob runs it. -
Send:
Please write
scripts/check-version-moved.sh <base branch>, which fails whenplugin/changed since that branch andplugin.json's version did not, passes when the plugin is new on the branch, and exits 2 if it cannot find the branch. -
Send:
Please add a
plugin-validjob toci.yml, withfetch-depth: 0, that runs both checks. In the same job, add a step that runs the standard's ownskills-ref validateon each skill, installed from the agentskills repository at a pinned commit, withcontinue-on-error: true, so it reports but does not block until someone has seen it pass on GitHub. My own check blocks. -
Test the check. Ask the agent to add
disable-model-invocation: truetoap-triage's frontmatter, runnpm run check:plugin, show you the failure, and remove the line. -
Ask the agent to add the plugin's code-running and configuration paths,
docs/ap-triage-rules.mdand each skill'sreferences/to.github/CODEOWNERS, and the plugin's risk tiers toREVIEW.md: skills and the changelog medium, and scripts, manifests, connector files, catalogs, settings and the rules sheets high. -
Ask the agent to open the pull request, with the template filled in.
-
Start a new session in the Code tab, so the reviewer has not seen the build, and send: "Please review the open pull request against
REVIEW.mdfrommain. Change nothing. List findings by risk tier, each with a file, a line and the input that would make it go wrong." -
Fix each real finding by directing the agent in your build session, push, resolve each conversation, and merge on GitHub.
-
Add
plugin-validto the ruleset's required checks. Read theskills-refstep's output in the job's log. If it passed, note it inNOTES.md, and a later pull request can make it blocking.
Checkpoint: merged, with checks, tests-unchanged, no-secrets and plugin-valid green, and plugin-valid required. Ask the agent to run git switch main and git pull.
Check it now. Download Task 15's answer key.
Task 16. The day the limit changes (30 minutes)
Goal: see what it takes to change a rule in every copy, and how anyone would know which copy ran (16.6). You rehearse on a branch you never merge.
Dave Kowalski, the controller, has been thinking of raising his limit to more than $10,000.00. It is only an idea. Before he decides, Ana wants to see what that day would take.
-
In
NOTES.md, predict the triage's last line under the new limit, and which check goes red first. Work out the new counts and totals from your Task 3 table. You need them in step 5. -
Keep a clerk who has not updated. Send: "In
ch16-install, please rungit switch main,git pullandnpm ci. Then leave that folder alone." -
Send: "Please make a new branch called
limit-changefrommain. We will never merge it." -
You will move the rule one file at a time. After each change, ask the agent to commit, then send this, and write in
NOTES.mdwhat went red:Please run
npm test,npm run test:py,npm run check:plugin,bash scripts/check-tests-unchanged.sh mainandbash scripts/check-version-moved.sh main, and tell me only which failed, with each one's message. -
Send these changes, in order:
- "In
docs/ap-triage-rules.md, please change rule 4 to more than $10,000.00, with exactly $10,000.00 not counting, its two boundary cases to $10,000.01 and $10,000.00, and the source line toRule 4's limit comes from AP policy v4, section 4.1. This is a rehearsal: Dave has not decided." - "Please copy that sheet over the one in the skill's
references/." - "Please change the rule 4 tests and the batch test to the new limit, in both languages, with my counts and totals." Paste yours under it.
- "Please change the limit in
src/triage.ts." - "Please change the limit in the skill's
scripts/triage.py." - "Please change the limit in
py/brightline_ap/triage.py."
- "In
-
tests-unchangedrefused your edited tests, which is its job. Send: "Please runALLOW_TEST_CHANGES=true bash scripts/check-tests-unchanged.sh main." On GitHub, thetests-changedlabel gives that setting. InNOTES.md, write the reason a pull request would give, naming AP policy v4. -
The sheet test is still red, because the sheet says AP policy v4 and the skill says v3. The version check has been red since the second change, and it asks only that the version moves. Choosing the number is a person's job, and this rehearsal uses 1.0.0, because the same invoice now lands in a different pile. Finish the release one step at a time, with the same checks after each:
- "In the skill's
metadata, please setversionto 1.0.0 andpolicyto AP policy v4." - "Please set
versionto 1.0.0 in both manifests." - "Please add a 1.0.0 entry dated 2027-03-08 to
plugin/CHANGELOG.md, naming AP policy v4."
In
NOTES.md, say why you chose 1.0.0 and not 0.1.1, using the third row of the table in 16.6. - "In the skill's
-
Send: "Please run
npm run triage -- data/invoices-intake.csv 2027-03-08here and inch16-install, and show me the first and last line of each." Copy the four lines intoNOTES.md. -
Send: "Please run
git switch mainandgit branch -D limit-change, then run the triage again."mainstill saysbrightline-ap 0.1.0,AP policy v3, and the counts from Task 4. Nothing was pushed.
Checkpoint: both pairs of lines are in NOTES.md, with a list of every check that went red and what turned it green.
Check it now. Download Task 16's answer key.
Sitting 3: port and use in six places (about 1 hour 40 minutes)
Read Concepts 16.7 and 16.10 first.
For each place, record three outcomes in ports/ch16.md, and keep them separate. Loaded: the product lists the three skills. Ran: the skill ran its tool or script, through the connector or with attached files. Verified: the triage counts for the intake batch on 2027-03-08 are 4, 2, 2 and 5, and the first line names the version it ran. Also note how a new version would reach that place, from 16.10. A place where the skills loaded but did not run is a partial port, and you write it down as one. Some places may not run a script, and some plans may not offer plugins. That is a result too, not a failure of the lab.
Task 17. The Claude plugin in a chat and a task (25 minutes)
Goal: the same plugin on two more Claude surfaces (16.7).
- Install the plugin on your claude.ai account from your GitHub repository's catalog, or upload a zip of the
plugin/folder. The steps are in 16.10. - Before any request, look under Customize > Connectors. If
apdatais listed, setwrite_file,edit_file,create_directoryandmove_fileto Blocked. A local connector may not list its tools there before it runs. If it does not, work in Manual, and decline every write. The plugin will also appear in Claude Code, where only a folder with the deny rules keeps them off. - In Claude chat, with code execution on, attach
invoices-intake.csv,vendors.csvandinvoices.csv, and ask: "Triage the attached batch for run date 2027-03-08." - In a task in the desktop app, with the
brightline-apfolder connected, ask which folders the apdata connector may read, then ask the same triage request. If your account has chat and tasks in one conversation, with no mode to choose, run this once, and record one place. On an account not moved over yet, the mode is called Cowork. - In
ports/ch16.md, record for each place which skills loaded, whether the connector loaded, which route the skill used, the repository or its own script, and the counts.
Checkpoint: the skill loads in both. The local connector is skipped in chat. In a task it loads in a session on your computer, which Team and Enterprise plans can choose. On Pro, Max and Ultra, new tasks run in the cloud, so record whether it loaded. The counts are 4, 2, 2 and 5 wherever the script ran.
Check it now. Download Task 17's answer key.
Task 18. Codex builds the OpenAI plugin (35 minutes)
Goal: the same skills packaged for OpenAI, by OpenAI's coding agent (16.7, 16.10).
-
Open the ChatGPT desktop app and choose Codex. Open the
brightline-apfolder, set Work in to This computer, and set the permission mode to Ask for approval. Send: "Please rungit switch mainandgit pull, then make a new branch calledopenai-plugin." -
Send:
The plugin in
plugin/already has an Agent Plugins 1.0.0 core:plugin/plugin.json,plugin/mcp.jsonandplugin/skills/. Check that Codex can load that core as it is. Add only what OpenAI needs beyond it: a catalog at.agents/plugins/marketplace.jsonnamedbrightline-openai, with an entry forbrightline-apfrom./plugin, and.codex/config.toml. In that file, enablebrightline-ap@brightline-openai, list the connectorapdata's four write tools indisabled_toolsfor that plugin, and declare a project server,[mcp_servers.apdata], that runsnpx -y @modelcontextprotocol/server-filesystem@2.1.0 ./data, with the samedisabled_tools. Do not create.codex-plugin/, do not copy or change any skill, and changeplugin.jsononly underextensions, if at all.Why a project server: Codex copies an installed plugin into its own cache and starts the plugin's connector there, where
./datanames no folder. A project server with the same name starts at the repository's root, and Codex uses it instead. -
Read the diff. Ask Codex to run
git diff --stat main, and check that nothing underplugin/skills/changed. -
Send: "Please run
codex plugin marketplace add ./, thencodex plugin add brightline-ap@brightline-openai." Adding a catalog does not install what it lists, so the second command matters. Trust the project when Codex asks, so it reads.codex/config.toml. Restart the ChatGPT desktop app, and start a new chat. -
Ask: "Using the apdata connector only, list the folders it may read." It should be your repository's
data/. If it is not, record what you saw inports/ch16.md. -
Type
$brightline-ap:invoice-triage, or pick it from the$list, and ask it to triage the intake batch for run date 2027-03-08. A plugin's skills carry the plugin's name, and a bare$invoice-triagedoes not start the skill. -
Optional: turn on Codex's OpenTelemetry log export in your user config for one run, and record in
ports/ch16.mdwhich of Task 13's observations it gives. -
Ask Codex to open the pull request, and merge it through the same checks.
Checkpoint: no change under plugin/skills/ or to the core manifest beyond extensions. Codex's counts are 4, 2, 2 and 5.
Check it now. Download Task 18's answer key.
Task 19. The same plugin in ChatGPT (15 minutes)
Goal: the OpenAI plugin on a second OpenAI surface.
- On the same computer as Task 18, open the ChatGPT desktop app, then Customize > Plugins, in Work if you still see that switch. A catalog you added with
codex plugin marketplace addappears there as a source, after you restart the app. Installbrightline-apfrom it, if Task 18 has not already. If your workspace allows plugins only through an admin, an admin imports your GitHub catalog instead, as 16.10 shows. If neither is open to you, skip this task and record why. - Start a new chat that runs locally, in the
brightline-approject, because a local connector needs your computer, and that project's.codex/config.tomlkeeps the write tools off. Attach the three files, and ask for triage with@invoice-triagefor run date 2027-03-08. - Record the three outcomes in
ports/ch16.md: what loaded, how the skill ran the tool, and the counts. OpenAI's pages say a longer task can run code and shell commands, so expect the script to run. If it did not, say so.
Checkpoint: the skill loads in ChatGPT. If it ran the script, the counts are 4, 2, 2 and 5. If it could not, ports/ch16.md says so, and the skill asked rather than guessed.
Check it now. Download Task 19's answer key.
Task 20. OpenCode builds its setup, and the tag (25 minutes)
Goal: the same skills in an open-source agent, then the handover. If Task 18 was your one port on the main route, skip steps 1 to 3 here.
-
Open the repository in OpenCode. Before you send anything, turn auto-accept off for this project, under Settings, then Projects. Then send: "Please run
git switch mainandgit pull, then make a new branch calledopencode-setup." -
Send:
Please add
opencode.jsoncat the repository root. Read skills from./plugin/skillswith askillslist, without copying them. Add the connector undermcp.serversasapdata, typelocal, runningnpx -y @modelcontextprotocol/server-filesystem@2.1.0 ./datafrom the repository root. Add permission rules: deny edits todata/**, ask for shell commands, ask before loading any skill, allowapdata_*, then denyapdata_write_file,apdata_edit_file,apdata_create_directoryandapdata_move_file. The last matching rule wins, so keep that order. -
Restart OpenCode in the folder, and approve the skill when asked. Ask which folders the apdata connector may read, then ask for triage of the intake batch for run date 2027-03-08. Expect a surprise on the folders: OpenCode sends the server its own folder list, which replaces
./data, so the connector can read the whole repository here. Write what it reports inports/ch16.md. -
Ask the agent to complete
ports/ch16.mdfrom your notes: one row per place, with the builder, the bundle it read, loaded, ran, verified, how a new version reaches it, and what had to change. Its headings are in this task's answer key, if you want them. -
Ask the agent to make a branch for this if you skipped step 1. Then ask it to update
NOTES.mdwith the plugin, the required checks and where each product's connector is made read-only, commit both notes files, open the pull request, and merge it. -
Send: "Please run
git switch mainandgit pull, thengit tag ch16andgit push origin ch16. Then clone the tag into a new folder next to this one, calledch16-check. Inch16-check, runnpm ci, anduv syncinpy/, then the four checks,npm run check:pluginandnpm run triage -- data/invoices-intake.csv 2027-03-08, and show me what each one prints." -
Clean up what you used, unless you will keep using it. If you did Task 17, uninstall the plugin from your claude.ai account. If you did Task 18, ask the agent to run
codex plugin remove brightline-ap@brightline-openai, thencodex plugin marketplace remove brightline-openai, in that order. Removing only the catalog leaves the skills loading from Codex's cache. If you keep them, keep the write tools blocked or denied in every place you use them.
Checkpoint: if you did this port, the same counts in OpenCode, its write tools gone, and the folder it really read written in your notes. On any route, the table complete for every place you used, and every check passing on ch16 from a clean clone.
Check it now. Download Task 20's answer key.
Sitting 4, at work: your own (optional, about an hour)
Task 21. Your own skill and plugin, with an agent (60 minutes)
Goal: the same steps on a job from your own work, so you can build the next skill without this lab (16.1 to 16.6).
-
Choose the job and its owner. Pick one procedure you or your team repeat, with numbers in it, and the person who owns its rules. Ask them for a short rules sheet and three cases they check by hand, with the answers. Make a new folder, have the agent put it under git, and save the sheet as
docs/rules.md. Use made-up or cleared data only. -
Place the pieces. Open the folder in the Code tab and send:
Please read
docs/rules.md. Ask me questions, one at a time, until you can list the job's steps, every number it produces, the things it must never do, and who acts on each result. Then show a table that places each part as a skill, tested code, a connector or a permission setting, with one reason each. Write no files yet.Answer as the owner would, or bring the owner in. Correct any placement you disagree with, then have the agent save the table in
NOTES.md. -
Tests, then code. Send:
Please write failing tests for the three cases in
docs/rules.md, taking every expected value from the sheet only. Show me the tests before you write them. Do not write the code yet.Ask the agent to run the tests, and see them fail for the right reason. Then ask for the smallest code that makes them pass, with every limit in a named constant, and a command that prints each number. Check that no test changed.
-
The skill. Send:
Please draft
plugin/skills/<job-name>/SKILL.mdto the Agent Skills specification, with only its six fields. Ask me first when people need it, when they must not use it, and who acts on each result. The description says what it does, when to use it and when not to. The body runs the tested command, copies its numbers exactly, and ends with who acts and a "Never" list. Put the rules sheet inreferences/. -
The plugin. Ask for
plugin/plugin.jsonfor Agent Plugins 1.0.0, with$schema, a name, version0.1.0and a description, and forplugin/.claude-plugin/plugin.jsonbeside it with the same name and version. Ask the agent to copyscripts/check_plugin.pyandschemas/from yourbrightline-aprepository, adapt the check to this folder, and run it until it reports no problems. -
Does it load? In the terminal, start a new session with
claude --permission-mode manual --plugin-dir ./plugin. Write five requests: three that should load the skill and two that should not. Run each twice, and fix the description, not the steps, until all ten runs behave. -
The owner's read. Show the owner the skill and the test cases. Write down what they changed. It will be something only they knew.
Checkpoint: failing tests committed before the code, a skill that passes the check and loads only when it should, a plugin that loads, and the owner's change recorded in NOTES.md.
Check it now. Download Task 21's answer key. It is a checklist, since the work is your own.
Domain expert and builder. The expert owns the rules, the cases, the order and who acts on each result. The builder directs the agent and owns what it produces: the tests, the code, the skill and the plugin. The builder also makes sure every "never" that must hold is a setting.
If something goes wrong
| Problem | What to try |
|---|---|
claude is not found | The desktop app does not include it. Install it, as "Before you start" says, and open a new terminal |
npm run triage cannot find a package | A new clone has no packages yet. Ask the agent to run npm ci |
A new test passes before src/triage.ts exists | It tests nothing. Check that it imports from ../src/triage.ts |
| The skill never loads | Read its description against the request. Put the main use first. Start a new session after every edit |
| The skill loads for "should we pay?" | The description lacks "never use it to decide whether to pay" |
claude plugin validate warns about an unknown field | Claude's manifest takes Claude's fields. The core takes the standard's. Do not mix them |
npm run check:plugin fails on a skill field | A field outside the six. Move a product's setting to where that product reads it (16.10) |
| The connector lists the wrong folder, or fails to start | ./data counts from where the server started. In Claude Code that is the session's folder, so start from the repository's root. A host can also send folders of its own, which replace the one on the command line. Write down what you saw |
| The connector will not start on Windows | Ask the agent to start npx through cmd /c, as the server's README says |
| The connector can write | The deny rules use the wrong tool names. Ask the agent to list the connector's tool names exactly as the product shows them |
/plugin says it is not available | It works in a session you start from a terminal, not in the Code tab |
| A task skips the connector | Its folder option has no default, or the task runs in the cloud, not on your computer |
| Chat says it cannot run the script | Turn on code execution, and attach all three files |
| Codex does not see the plugin | Check that codex plugin list says installed. If it says not installed, run codex plugin add brightline-ap@brightline-openai. Then restart the ChatGPT desktop app, trust the project, and start a new chat |
codex plugin marketplace list shows brightline, not brightline-openai | You added the folder before .agents/plugins/marketplace.json existed, so Codex used Claude's catalog. Remove brightline, and add the folder again |
Codex says apdata failed to start | That is the plugin's copy of the connector, in Codex's cache. Check that .codex/config.toml declares the project server, and that you trusted the project |
The skills-ref step shows a failure but the job is green | It is advisory (Task 15). Read its message. Your own check:plugin is the blocking check |
| OpenCode loads no skill | Check the skills path, and that a skill rule does not deny it |
OpenCode says it has no apdata | The server may still be starting. Wait ten seconds, and ask again |
| The telemetry file has no records | The variables were not set on the same command that started claude |
Without a paid Claude plan
Do Tasks 1 to 5, 7 to 12 and 14 to 16 in Codex or OpenCode, with its safety settings from 16.10 set first. Skip each step that runs the claude command, but still make the ch16-install clone in Task 11, because Task 16 uses it. In Task 1, ask the agent to run npm run due -- 2027-03-12. Write Claude's manifest and connector file anyway, and let npm run check:plugin check them. Then do the port for your tool, Task 18 or Task 20, and run Tasks 6, 8 and 9's requests there. Say in ports/ch16.md that Claude Code was not run.
Artifact checklist
Tick what your route covered.
- Your first skill's answer checked against the command (Task 1)
- Your repository on GitHub, with
mainprotected and the three checks required (Task 10) - Your Task 2 table, Task 6 load table, Task 12 cases and Task 13 telemetry note in
NOTES.md -
tests/triage.test.ts, committed failing beforesrc/triage.ts, andnpm run triage -
plugin/skills/withinvoice-triage,ap-triageanddue-by-friday, six fields each, none working out a date or number itself, the parity and sheet tests, and a first line that names the version and the policy -
plugin/plugin.jsonandplugin/mcp.json(Agent Plugins 1.0.0),plugin/.claude-plugin/plugin.jsonandplugin/.mcp.json - The connector pinned at 2.1.0, and its write tools denied in
.claude/settings.json,.codex/config.tomlandopencode.jsonc, and blocked in claude.ai's connector settings for chat and tasks -
plugin/CHANGELOG.md,.claude-plugin/marketplace.json(catalogbrightline),.agents/plugins/marketplace.json(catalogbrightline-openai), andevals/README.md - Your decision on
exercises/ap-helper-plugin/, with the line that decided it -
plugin-validas a required check, with its tests, the version check and its two Python dependencies locked - Your Task 16 notes: the two first lines, the two last lines, and every check that went red and what turned it green
-
ports/ch16.mdwith each place you used, each with loaded, ran and verified recorded separately, and how a new version reaches it, and tagch16, checked from a clean clone - Task 21, at work: your own tests, skill and plugin, built with an agent, with the owner's change recorded
Sources
- Owner
- Panaversity